gitpr-cli 0.0.11__tar.gz → 0.0.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitpr_cli-0.0.11/src/gitpr_cli.egg-info → gitpr_cli-0.0.13}/PKG-INFO +1 -1
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13/gitpr_cli.egg-info}/PKG-INFO +1 -1
- gitpr_cli-0.0.13/gitpr_cli.egg-info/SOURCES.txt +19 -0
- gitpr_cli-0.0.13/gitpr_cli.egg-info/top_level.txt +1 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/pyproject.toml +5 -2
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/core.py +11 -5
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/main.py +7 -11
- gitpr_cli-0.0.13/src/updater.py +167 -0
- gitpr_cli-0.0.11/src/gitpr_cli.egg-info/SOURCES.txt +0 -19
- gitpr_cli-0.0.11/src/gitpr_cli.egg-info/top_level.txt +0 -9
- gitpr_cli-0.0.11/src/updater.py +0 -90
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/LICENSE +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/README.md +0 -0
- {gitpr_cli-0.0.11/src → gitpr_cli-0.0.13}/gitpr_cli.egg-info/dependency_links.txt +0 -0
- {gitpr_cli-0.0.11/src → gitpr_cli-0.0.13}/gitpr_cli.egg-info/entry_points.txt +0 -0
- {gitpr_cli-0.0.11/src → gitpr_cli-0.0.13}/gitpr_cli.egg-info/requires.txt +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/setup.cfg +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/__init__.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/ai_providers.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/cache.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/config.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/linter_engine.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/src/security.py +0 -0
- {gitpr_cli-0.0.11 → gitpr_cli-0.0.13}/tests/test_core.py +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gitpr_cli.egg-info/PKG-INFO
|
|
5
|
+
gitpr_cli.egg-info/SOURCES.txt
|
|
6
|
+
gitpr_cli.egg-info/dependency_links.txt
|
|
7
|
+
gitpr_cli.egg-info/entry_points.txt
|
|
8
|
+
gitpr_cli.egg-info/requires.txt
|
|
9
|
+
gitpr_cli.egg-info/top_level.txt
|
|
10
|
+
src/__init__.py
|
|
11
|
+
src/ai_providers.py
|
|
12
|
+
src/cache.py
|
|
13
|
+
src/config.py
|
|
14
|
+
src/core.py
|
|
15
|
+
src/linter_engine.py
|
|
16
|
+
src/main.py
|
|
17
|
+
src/security.py
|
|
18
|
+
src/updater.py
|
|
19
|
+
tests/test_core.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitpr-cli"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.13"
|
|
8
8
|
description = "Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,4 +21,7 @@ dependencies = [
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[project.scripts]
|
|
24
|
-
gitpr = "src.main:cli"
|
|
24
|
+
gitpr = "src.main:cli"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
packages = ["src"]
|
|
@@ -15,15 +15,16 @@ from src.ai_providers import call_ai_model
|
|
|
15
15
|
def get_git_diff():
|
|
16
16
|
"""Executa 'git diff HEAD' e retorna a saída, alertando sobre arquivos não monitorados (untracked)."""
|
|
17
17
|
try:
|
|
18
|
-
# Verifica se existem arquivos novos não monitorados (untracked)
|
|
18
|
+
# 1. Verifica se existem arquivos novos não monitorados (untracked)
|
|
19
19
|
untracked_process = subprocess.run(
|
|
20
20
|
["git", "ls-files", "--others", "--exclude-standard"],
|
|
21
21
|
capture_output=True,
|
|
22
22
|
text=True,
|
|
23
|
-
encoding="utf-8"
|
|
23
|
+
encoding="utf-8",
|
|
24
|
+
errors="replace" # <--- CORREÇÃO AQUI (Evita crash com acentos)
|
|
24
25
|
)
|
|
25
26
|
untracked_files = untracked_process.stdout.strip()
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
# Se houver arquivos novos, exibe um alerta educativo no console
|
|
28
29
|
if untracked_files:
|
|
29
30
|
click.secho("⚠️ Aviso: O Git detectou novos arquivos que não estão sendo monitorados:", fg="yellow")
|
|
@@ -31,13 +32,14 @@ def get_git_diff():
|
|
|
31
32
|
click.secho(f" - {file}", fg="yellow", dim=True)
|
|
32
33
|
click.secho("💡 Dica: Use 'git add <arquivo>' para que eles sejam incluídos na análise do GitPR.", fg="cyan")
|
|
33
34
|
click.secho("📚 Entenda o motivo: https://github.com/natanfiuza/gitpr/blob/main/docs/untracked-files.md\n", fg="blue", underline=True)
|
|
34
|
-
|
|
35
|
-
#
|
|
35
|
+
|
|
36
|
+
# 2. Executa o diff normal que captura arquivos monitorados e em staging
|
|
36
37
|
result = subprocess.run(
|
|
37
38
|
["git", "diff", "HEAD"],
|
|
38
39
|
capture_output=True,
|
|
39
40
|
text=True,
|
|
40
41
|
encoding="utf-8",
|
|
42
|
+
errors="replace", # <--- CORREÇÃO AQUI (Evita crash com acentos)
|
|
41
43
|
check=True
|
|
42
44
|
)
|
|
43
45
|
return result.stdout
|
|
@@ -98,6 +100,7 @@ def get_skill_context(action_type="pr"):
|
|
|
98
100
|
# Retorna vazio se não existir
|
|
99
101
|
return ""
|
|
100
102
|
|
|
103
|
+
|
|
101
104
|
def generate_pr_content(action_folder, action_type, diff_text, provider="gemini"):
|
|
102
105
|
"""Envia o diff para a IA usando System Instruction e retorna um JSON parseado."""
|
|
103
106
|
if not diff_text or not diff_text.strip():
|
|
@@ -168,6 +171,7 @@ def generate_pr_content(action_folder, action_type, diff_text, provider="gemini"
|
|
|
168
171
|
|
|
169
172
|
return None
|
|
170
173
|
|
|
174
|
+
|
|
171
175
|
def generate_skill_template():
|
|
172
176
|
"""
|
|
173
177
|
Faz o download dos templates .gitpr.pr.md, .gitpr.review.md
|
|
@@ -219,6 +223,7 @@ def generate_skill_template():
|
|
|
219
223
|
else:
|
|
220
224
|
click.echo("\nNenhum arquivo novo foi baixado.")
|
|
221
225
|
|
|
226
|
+
|
|
222
227
|
def get_base_branch():
|
|
223
228
|
"""Descobre a branch principal remota (ex: main ou master)."""
|
|
224
229
|
try:
|
|
@@ -233,6 +238,7 @@ def get_base_branch():
|
|
|
233
238
|
click.secho("⚠️ Aviso: Branch principal remota não detectada. Assumindo 'main' como fallback padrão.", fg="yellow")
|
|
234
239
|
return "main" # Fallback padrão caso não encontre
|
|
235
240
|
|
|
241
|
+
|
|
236
242
|
def get_git_full_diff():
|
|
237
243
|
"""Faz o fetch e captura o diff entre a branch principal remota e o estado atual."""
|
|
238
244
|
click.secho("🔄 Sincronizando com o repositório remoto (git fetch)...", fg="cyan")
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
|
|
6
6
|
# Importações dos nossos módulos internos atualizadas
|
|
7
7
|
from src.config import setup_environment, check_internet_connection, get_ai_provider
|
|
8
|
-
from src.updater import check_and_update, __version__
|
|
8
|
+
from src.updater import check_and_update, __version__, print_update_notice
|
|
9
9
|
from src.core import (
|
|
10
10
|
get_git_diff,
|
|
11
11
|
get_git_full_diff,
|
|
@@ -109,17 +109,10 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
|
|
|
109
109
|
check_internet_connection()
|
|
110
110
|
|
|
111
111
|
# Módulo de Atualização (Pip-Aware)
|
|
112
|
-
if update:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
check_and_update()
|
|
116
|
-
else:
|
|
117
|
-
click.secho("💡 Como você instalou via PIP, atualize rodando: pip install --upgrade gitpr-cli", fg="cyan", bold=True)
|
|
112
|
+
if update:
|
|
113
|
+
click.secho("🔍 Verificando atualizações...", fg="cyan")
|
|
114
|
+
check_and_update()
|
|
118
115
|
return
|
|
119
|
-
else:
|
|
120
|
-
# Verificação automática em segundo plano a cada uso (Apenas binário)
|
|
121
|
-
if is_compiled:
|
|
122
|
-
check_and_update()
|
|
123
116
|
|
|
124
117
|
# Opção --skill: Gera o template e encerra
|
|
125
118
|
if skill:
|
|
@@ -294,6 +287,9 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
|
|
|
294
287
|
click.secho(f"\n✅ Sucesso! O arquivo '{output_filename}' foi gerado na pasta atual.", fg="green", bold=True)
|
|
295
288
|
except Exception as e:
|
|
296
289
|
click.secho(f"\n❌ Erro ao guardar o arquivo: {e}", fg="red")
|
|
290
|
+
|
|
291
|
+
if not quiet:
|
|
292
|
+
print_update_notice()
|
|
297
293
|
|
|
298
294
|
if __name__ == "__main__":
|
|
299
295
|
cli()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import urllib.request
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import shutil
|
|
6
|
+
import click
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
# Versão atual do seu executável local (Atualize isso a cada novo build!)
|
|
10
|
+
__version__ = "0.0.13"
|
|
11
|
+
GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
|
|
12
|
+
PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
|
|
13
|
+
|
|
14
|
+
def get_gitpr_dir():
|
|
15
|
+
"""Retorna o diretório ~/.gitpr/"""
|
|
16
|
+
return os.path.join(os.path.expanduser("~"), ".gitpr")
|
|
17
|
+
|
|
18
|
+
def get_update_cache_file():
|
|
19
|
+
"""Retorna o caminho do arquivo de cache de atualizações."""
|
|
20
|
+
return os.path.join(get_gitpr_dir(), "update_cache.json")
|
|
21
|
+
|
|
22
|
+
def parse_version(version_str):
|
|
23
|
+
"""Converte 'v0.1.0' ou '0.1.0' em uma tupla (0, 1, 0) para matemática de versões."""
|
|
24
|
+
clean_version = version_str.lower().replace("v", "")
|
|
25
|
+
try:
|
|
26
|
+
return tuple(map(int, clean_version.split(".")))
|
|
27
|
+
except ValueError:
|
|
28
|
+
return (0, 0, 0)
|
|
29
|
+
|
|
30
|
+
def get_latest_remote_version(is_compiled):
|
|
31
|
+
"""Busca a última versão na API correta (PyPI ou GitHub) com cache diário."""
|
|
32
|
+
cache_file = get_update_cache_file()
|
|
33
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
34
|
+
|
|
35
|
+
# 1. Tenta ler do cache para não atrasar o terminal do usuário
|
|
36
|
+
if os.path.exists(cache_file):
|
|
37
|
+
try:
|
|
38
|
+
with open(cache_file, "r") as f:
|
|
39
|
+
cache_data = json.load(f)
|
|
40
|
+
if cache_data.get("date") == today:
|
|
41
|
+
return cache_data.get("version"), cache_data.get("download_url")
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
# 2. Busca na Web se o cache expirou
|
|
46
|
+
latest_version = ""
|
|
47
|
+
download_url = ""
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
if is_compiled:
|
|
51
|
+
# Busca no GitHub (Para executável)
|
|
52
|
+
req = urllib.request.Request(GITHUB_API_URL, headers={'User-Agent': 'GitPR-Updater'})
|
|
53
|
+
with urllib.request.urlopen(req, timeout=3) as response:
|
|
54
|
+
data = json.loads(response.read().decode())
|
|
55
|
+
latest_version = data.get("tag_name", "").replace("v", "")
|
|
56
|
+
|
|
57
|
+
assets = data.get("assets", [])
|
|
58
|
+
exe_asset = next((a for a in assets if a.get("name") == "gitpr.exe"), None)
|
|
59
|
+
if exe_asset:
|
|
60
|
+
download_url = exe_asset.get("browser_download_url")
|
|
61
|
+
else:
|
|
62
|
+
# Busca no PyPI (Para instalação via PIP)
|
|
63
|
+
req = urllib.request.Request(PYPI_API_URL, headers={'User-Agent': 'GitPR-Updater'})
|
|
64
|
+
with urllib.request.urlopen(req, timeout=3) as response:
|
|
65
|
+
data = json.loads(response.read().decode())
|
|
66
|
+
latest_version = data.get("info", {}).get("version", "")
|
|
67
|
+
|
|
68
|
+
# 3. Salva no cache
|
|
69
|
+
if latest_version:
|
|
70
|
+
os.makedirs(get_gitpr_dir(), exist_ok=True)
|
|
71
|
+
with open(cache_file, "w") as f:
|
|
72
|
+
json.dump({"date": today, "version": latest_version, "download_url": download_url}, f)
|
|
73
|
+
|
|
74
|
+
except Exception:
|
|
75
|
+
pass # Falha silenciosa em caso de falta de internet
|
|
76
|
+
|
|
77
|
+
return latest_version, download_url
|
|
78
|
+
|
|
79
|
+
def print_update_notice():
|
|
80
|
+
"""Imprime o bloco de aviso estilo PIP no fim da execução."""
|
|
81
|
+
is_compiled = getattr(sys, 'frozen', False)
|
|
82
|
+
latest_version, _ = get_latest_remote_version(is_compiled)
|
|
83
|
+
|
|
84
|
+
if not latest_version:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
current_v = parse_version(__version__)
|
|
88
|
+
latest_v = parse_version(latest_version)
|
|
89
|
+
|
|
90
|
+
if latest_v > current_v:
|
|
91
|
+
click.echo("")
|
|
92
|
+
click.secho(f"[notice] A new release of gitpr is available: {__version__} -> {latest_version}", fg="yellow", dim=True)
|
|
93
|
+
if is_compiled:
|
|
94
|
+
click.secho(f"[notice] To update, run: gitpr --update", fg="yellow", dim=True)
|
|
95
|
+
else:
|
|
96
|
+
click.secho(f"[notice] To update, run: pip install --upgrade gitpr-cli", fg="yellow", dim=True)
|
|
97
|
+
click.echo("")
|
|
98
|
+
|
|
99
|
+
def check_and_update():
|
|
100
|
+
"""Função disparada apenas quando o usuário força a flag --update."""
|
|
101
|
+
is_compiled = getattr(sys, 'frozen', False)
|
|
102
|
+
|
|
103
|
+
if not is_compiled:
|
|
104
|
+
click.secho("💡 Como você instalou via PIP, atualize rodando: pip install --upgrade gitpr-cli", fg="cyan", bold=True)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
latest_version, download_url = get_latest_remote_version(is_compiled=True)
|
|
108
|
+
|
|
109
|
+
if not latest_version or not download_url:
|
|
110
|
+
click.secho("❌ Não foi possível verificar atualizações no momento.", fg="red")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
current_v = parse_version(__version__)
|
|
114
|
+
latest_v = parse_version(latest_version)
|
|
115
|
+
|
|
116
|
+
if latest_v > current_v:
|
|
117
|
+
click.secho(f"\n🚀 Nova versão do GitPR encontrada (v{latest_version})!", fg="green", bold=True)
|
|
118
|
+
click.secho("Baixando atualização em segundo plano...", fg="cyan")
|
|
119
|
+
_perform_hot_swap(download_url)
|
|
120
|
+
else:
|
|
121
|
+
click.secho("✅ Você já está usando a versão mais recente do GitPR.", fg="green")
|
|
122
|
+
|
|
123
|
+
def _perform_hot_swap(download_url):
|
|
124
|
+
"""Faz o download e substitui o executável atual."""
|
|
125
|
+
current_exe = sys.executable
|
|
126
|
+
old_exe = current_exe + ".old"
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if os.path.exists(old_exe):
|
|
130
|
+
os.remove(old_exe)
|
|
131
|
+
os.rename(current_exe, old_exe)
|
|
132
|
+
urllib.request.urlretrieve(download_url, current_exe)
|
|
133
|
+
click.secho(f"✅ Atualização concluída com sucesso! Na próxima execução você já usará a nova versão.\n", fg="green", bold=True)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
click.secho(f"❌ Falha ao aplicar atualização: {e}", fg="red")
|
|
136
|
+
if os.path.exists(old_exe) and not os.path.exists(current_exe):
|
|
137
|
+
os.rename(old_exe, current_exe)
|
|
138
|
+
"""Faz o download e substitui o executável atual (Hot-Swap)."""
|
|
139
|
+
current_exe = sys.executable
|
|
140
|
+
|
|
141
|
+
# Se não estiver rodando como executável compilado (PyInstaller), aborta o update
|
|
142
|
+
if not getattr(sys, 'frozen', False):
|
|
143
|
+
click.secho("⚠️ Aviso: Rodando via script Python. O Auto-Update funciona apenas no executável compilado.", fg="yellow")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
old_exe = current_exe + ".old"
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# 1. Renomeia o executável atual que está em uso
|
|
150
|
+
if os.path.exists(old_exe):
|
|
151
|
+
os.remove(old_exe) # Remove restos antigos se existirem
|
|
152
|
+
os.rename(current_exe, old_exe)
|
|
153
|
+
|
|
154
|
+
# 2. Faz o download direto para o caminho original
|
|
155
|
+
urllib.request.urlretrieve(download_url, current_exe)
|
|
156
|
+
|
|
157
|
+
# 3. Salva o novo hash
|
|
158
|
+
with open(sha_file, "w") as f:
|
|
159
|
+
f.write(new_digest)
|
|
160
|
+
|
|
161
|
+
click.secho(f"✅ Atualização concluída com sucesso! Na próxima execução você já usará a nova versão.\n", fg="green", bold=True)
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
# Se algo falhar na renomeação/download, tenta desfazer a bagunça
|
|
165
|
+
click.secho(f"❌ Falha ao aplicar atualização: {e}", fg="red")
|
|
166
|
+
if os.path.exists(old_exe) and not os.path.exists(current_exe):
|
|
167
|
+
os.rename(old_exe, current_exe)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
src/__init__.py
|
|
5
|
-
src/ai_providers.py
|
|
6
|
-
src/cache.py
|
|
7
|
-
src/config.py
|
|
8
|
-
src/core.py
|
|
9
|
-
src/linter_engine.py
|
|
10
|
-
src/main.py
|
|
11
|
-
src/security.py
|
|
12
|
-
src/updater.py
|
|
13
|
-
src/gitpr_cli.egg-info/PKG-INFO
|
|
14
|
-
src/gitpr_cli.egg-info/SOURCES.txt
|
|
15
|
-
src/gitpr_cli.egg-info/dependency_links.txt
|
|
16
|
-
src/gitpr_cli.egg-info/entry_points.txt
|
|
17
|
-
src/gitpr_cli.egg-info/requires.txt
|
|
18
|
-
src/gitpr_cli.egg-info/top_level.txt
|
|
19
|
-
tests/test_core.py
|
gitpr_cli-0.0.11/src/updater.py
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import shutil
|
|
6
|
-
import click
|
|
7
|
-
|
|
8
|
-
# Versão atual do seu executável local (Atualize isso a cada novo build!)
|
|
9
|
-
__version__ = "0.0.11"
|
|
10
|
-
GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
|
|
11
|
-
|
|
12
|
-
def get_gitpr_dir():
|
|
13
|
-
"""Retorna o diretório ~/.gitpr/"""
|
|
14
|
-
return os.path.join(os.path.expanduser("~"), ".gitpr")
|
|
15
|
-
|
|
16
|
-
def check_and_update():
|
|
17
|
-
"""Verifica na API do GitHub se há um novo release com um hash diferente."""
|
|
18
|
-
try:
|
|
19
|
-
# Timeout curto para não travar a CLI se a API do GitHub estiver lenta
|
|
20
|
-
req = urllib.request.Request(GITHUB_API_URL, headers={'User-Agent': 'GitPR-Updater'})
|
|
21
|
-
with urllib.request.urlopen(req, timeout=3) as response:
|
|
22
|
-
data = json.loads(response.read().decode())
|
|
23
|
-
|
|
24
|
-
# Pega a versão remota (apenas para log/exibição)
|
|
25
|
-
remote_version = data.get("tag_name", "").replace("v", "")
|
|
26
|
-
|
|
27
|
-
# Procura o executável nos assets
|
|
28
|
-
assets = data.get("assets", [])
|
|
29
|
-
exe_asset = next((a for a in assets if a.get("name") == "gitpr.exe"), None)
|
|
30
|
-
|
|
31
|
-
if not exe_asset:
|
|
32
|
-
return # Nenhum executável encontrado no release mais recente
|
|
33
|
-
|
|
34
|
-
remote_digest = exe_asset.get("digest", "").replace("sha256:", "")
|
|
35
|
-
download_url = exe_asset.get("browser_download_url")
|
|
36
|
-
|
|
37
|
-
if not remote_digest or not download_url:
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
# Compara com o hash local
|
|
41
|
-
gitpr_dir = get_gitpr_dir()
|
|
42
|
-
sha_file = os.path.join(gitpr_dir, ".sha256")
|
|
43
|
-
local_digest = ""
|
|
44
|
-
|
|
45
|
-
if os.path.exists(sha_file):
|
|
46
|
-
with open(sha_file, "r") as f:
|
|
47
|
-
local_digest = f.read().strip()
|
|
48
|
-
|
|
49
|
-
# Se os hashes forem diferentes, dispara o update!
|
|
50
|
-
if remote_digest != local_digest:
|
|
51
|
-
click.secho(f"\n🚀 Nova versão do GitPR encontrada (v{remote_version})!", fg="green", bold=True)
|
|
52
|
-
click.secho("Baixando atualização em segundo plano...", fg="cyan")
|
|
53
|
-
_perform_hot_swap(download_url, remote_digest, sha_file)
|
|
54
|
-
|
|
55
|
-
except Exception as e:
|
|
56
|
-
# Silencia erros de timeout ou rede para não atrapalhar o fluxo do usuário
|
|
57
|
-
click.secho(f"Debug Updater: {e}", fg="red") # Descomente para debugar
|
|
58
|
-
pass
|
|
59
|
-
|
|
60
|
-
def _perform_hot_swap(download_url, new_digest, sha_file):
|
|
61
|
-
"""Faz o download e substitui o executável atual (Hot-Swap)."""
|
|
62
|
-
current_exe = sys.executable
|
|
63
|
-
|
|
64
|
-
# Se não estiver rodando como executável compilado (PyInstaller), aborta o update
|
|
65
|
-
if not getattr(sys, 'frozen', False):
|
|
66
|
-
click.secho("⚠️ Aviso: Rodando via script Python. O Auto-Update funciona apenas no executável compilado.", fg="yellow")
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
old_exe = current_exe + ".old"
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
# 1. Renomeia o executável atual que está em uso
|
|
73
|
-
if os.path.exists(old_exe):
|
|
74
|
-
os.remove(old_exe) # Remove restos antigos se existirem
|
|
75
|
-
os.rename(current_exe, old_exe)
|
|
76
|
-
|
|
77
|
-
# 2. Faz o download direto para o caminho original
|
|
78
|
-
urllib.request.urlretrieve(download_url, current_exe)
|
|
79
|
-
|
|
80
|
-
# 3. Salva o novo hash
|
|
81
|
-
with open(sha_file, "w") as f:
|
|
82
|
-
f.write(new_digest)
|
|
83
|
-
|
|
84
|
-
click.secho(f"✅ Atualização concluída com sucesso! Na próxima execução você já usará a nova versão.\n", fg="green", bold=True)
|
|
85
|
-
|
|
86
|
-
except Exception as e:
|
|
87
|
-
# Se algo falhar na renomeação/download, tenta desfazer a bagunça
|
|
88
|
-
click.secho(f"❌ Falha ao aplicar atualização: {e}", fg="red")
|
|
89
|
-
if os.path.exists(old_exe) and not os.path.exists(current_exe):
|
|
90
|
-
os.rename(old_exe, current_exe)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|