diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..0d49d5f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +python scripts/validate_repo.py diff --git a/README.md b/README.md index 1b6d253..6d6f308 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,34 @@ python -m compileall main.py src ``` O script remove marcadores de conflito e artefactos de branch comuns e cria backup automático (`src/app.py.bak`). +codex/review-repository-code-p61gr6 + + +## Prevenção de conflitos (recomendado) + +Ativa validação local automática antes de cada commit: + +```bash +git config core.hooksPath .githooks +``` + +Ativa também opções de Git que ajudam em conflitos repetidos: + +```bash +git config rerere.enabled true +git config merge.conflictstyle zdiff3 +git config pull.rebase true +``` + +Fluxo recomendado para reduzir conflitos em `src/app.py`: + +```bash +git checkout main +git pull origin main +git checkout -b fix/minha-alteracao +# editar +git add . +git commit -m "..." +git fetch origin +git rebase origin/main +``` diff --git a/scripts/fix_conflict_artifacts.py b/scripts/fix_conflict_artifacts.py index b47addd..72075ae 100644 --- a/scripts/fix_conflict_artifacts.py +++ b/scripts/fix_conflict_artifacts.py @@ -2,10 +2,36 @@ from pathlib import Path import argparse + codex/review-repository-code-p61gr6 +import re import shutil import sys CONFLICT_PREFIXES = ("<<<<<<<", "=======", ">>>>>>>") +codex/review-repository-code-p61gr6 +CODEX_TOKEN = "codex/review-repository-code-" +BRANCH_LABEL_RE = re.compile(r"^(main|master|develop|release/.+|hotfix/.+)$") + + +def should_remove_line(lines: list[str], index: int) -> bool: + raw_line = lines[index] + stripped = raw_line.strip() + if not stripped: + return False + + if stripped.startswith(CONFLICT_PREFIXES): + return True + + if CODEX_TOKEN in stripped: + return True + + if BRANCH_LABEL_RE.match(stripped): + before = lines[index - 1].strip() if index - 1 >= 0 else "" + after = lines[index + 1].strip() if index + 1 < len(lines) else "" + if CODEX_TOKEN in before or CODEX_TOKEN in after or any( + token in before or token in after for token in CONFLICT_PREFIXES + ): + return True def should_remove_line(raw_line: str) -> bool: @@ -18,6 +44,7 @@ def should_remove_line(raw_line: str) -> bool: return True if stripped == "main": return True + return False @@ -26,8 +53,13 @@ def sanitize_file(path: Path, write: bool) -> tuple[int, int]: cleaned: list[str] = [] removed = 0 + codex/review-repository-code-p61gr6 + for idx, line in enumerate(lines): + if should_remove_line(lines, idx): + for line in lines: if should_remove_line(line): + removed += 1 continue cleaned.append(line) @@ -54,7 +86,12 @@ def main() -> int: removed, total = sanitize_file(target, write=args.write) if args.write: if removed: + codex/review-repository-code-p61gr6 + backup = target.with_suffix(target.suffix + ".bak") + print(f"Removidas {removed} linha(s) suspeita(s) de {total}. Backup criado em {backup}") + print(f"Removidas {removed} linha(s) suspeita(s) de {total}. Backup criado em {target.with_suffix(target.suffix + '.bak')}") + else: print("Nenhuma linha suspeita encontrada. Nada para alterar.") else: diff --git a/scripts/validate_repo.py b/scripts/validate_repo.py index b263a19..18ab5fc 100644 --- a/scripts/validate_repo.py +++ b/scripts/validate_repo.py @@ -1,36 +1,70 @@ from __future__ import annotations - from pathlib import Path +import ast import compileall +import re import sys ROOT = Path(__file__).resolve().parents[1] CONFLICT_TOKENS = ("<<<<<<<", "=======", ">>>>>>>") +CODEX_TOKEN = "codex/review-repository-code-" +BRANCH_LABEL_RE = re.compile(r"^(main|master|develop|release/.+|hotfix/.+)$") + + +def target_files() -> list[Path]: + return [ROOT / "main.py", *(ROOT / "src").rglob("*.py")] def check_conflict_markers() -> list[str]: problems: list[str] = [] - targets = [ROOT / "main.py", *(ROOT / "src").rglob("*.py")] - for path in targets: - text = path.read_text(encoding="utf-8", errors="ignore") - for idx, line in enumerate(text.splitlines(), start=1): - if any(token in line for token in CONFLICT_TOKENS): + for path in target_files(): + lines = path.read_text(encoding="utf-8", errors="ignore").splitlines() + for idx, raw in enumerate(lines, start=1): + stripped = raw.strip() + if any(token in raw for token in CONFLICT_TOKENS): problems.append(f"{path.relative_to(ROOT)}:{idx}: marcador de conflito encontrado") - if line.strip().startswith("codex/review-repository-code-"): + continue + + if CODEX_TOKEN in stripped: problems.append(f"{path.relative_to(ROOT)}:{idx}: artefacto de branch encontrado") + continue + + # Linha de branch "solta" típica de conflito mal resolvido. + if BRANCH_LABEL_RE.match(stripped): + before = lines[idx - 2].strip() if idx - 2 >= 0 else "" + after = lines[idx].strip() if idx < len(lines) else "" + if CODEX_TOKEN in before or CODEX_TOKEN in after or any( + tok in before or tok in after for tok in CONFLICT_TOKENS + ): + problems.append(f"{path.relative_to(ROOT)}:{idx}: etiqueta de branch solta encontrada") return problems +def check_python_syntax() -> list[str]: + errors: list[str] = [] + for path in target_files(): + source = path.read_text(encoding="utf-8", errors="ignore") + try: + ast.parse(source, filename=str(path.relative_to(ROOT))) + except SyntaxError as exc: + line = exc.lineno or 1 + msg = exc.msg or "erro de sintaxe" + errors.append(f"{path.relative_to(ROOT)}:{line}: {msg}") + return errors + + def main() -> int: marker_errors = check_conflict_markers() - if marker_errors: - for error in marker_errors: + syntax_errors = check_python_syntax() + + if marker_errors or syntax_errors: + for error in marker_errors + syntax_errors: print(error) return 1 - ok = compileall.compile_dir(str(ROOT / "src"), quiet=1) + ok_src = compileall.compile_dir(str(ROOT / "src"), quiet=1) ok_main = compileall.compile_file(str(ROOT / "main.py"), quiet=1) - if not ok or not ok_main: + if not ok_src or not ok_main: print("Falha de compilação Python.") return 1 diff --git a/src/app.py b/src/app.py index 8c65c4f..35fa0f8 100644 --- a/src/app.py +++ b/src/app.py @@ -33,6 +33,7 @@ def _migrate_legacy_config_if_needed(): if CONFIG_FILE.exists() or not LEGACY_CONFIG_FILE.exists(): return + codex/review-repository-code-p61gr6 codex/review-repository-code-hpjdax codex/review-repository-code-argiid @@ -46,9 +47,6 @@ def _migrate_legacy_config_if_needed(): def _read_json_file(path: Path): """Le JSON de um ficheiro devolvendo dict vazio em caso de erro.""" - try: - - main try: legacy_data = json.loads(LEGACY_CONFIG_FILE.read_text(encoding="utf-8")) APP_CONFIG_DIR.mkdir(parents=True, exist_ok=True) @@ -60,10 +58,11 @@ def _read_json_file(path: Path): def _read_json_file(path: Path): """Le JSON de um ficheiro devolvendo dict vazio em caso de erro.""" try: + codex/review-repository-code-p61gr6 + codex/review-repository-code-hpjdax -main if path.exists(): with open(path, "r", encoding="utf-8") as f: return json.load(f) @@ -142,6 +141,9 @@ def __init__(self): self.config = load_config() self.scan_paths = [] self._selection_loaded = False +codex/review-repository-code-p61gr6 + self.filters_panel_visible = True + codex/review-repository-code-hpjdax self.filters_panel_visible = True @@ -155,7 +157,6 @@ def __init__(self): self.filters_panel_visible = True - main self.setWindowTitle("Duplicate File Cleaner") self.setMinimumSize(900, 620) @@ -416,6 +417,9 @@ def _build_scan_tab(self): self.scan_mode.currentIndexChanged.connect(self._on_mode_change) mode_layout.addWidget(self.scan_mode) mode_layout.addStretch() +codex/review-repository-code-p61gr6 + + codex/review-repository-code-hpjdax @@ -431,11 +435,13 @@ def _build_scan_tab(self): codex/review-repository-code-l1cd2s - main + self.toggle_filters_btn = QPushButton("Esconder Selecao") self.toggle_filters_btn.clicked.connect(self._toggle_filters_panel) mode_layout.addWidget(self.toggle_filters_btn) - filter_layout.addLayout(mode_layout) + filter_layout.addLayout(mode_layout) codex/review-repository-code-p61gr6 + self.filters_panel_widget = self._build_filters_panel() + filter_layout.addWidget(self.filters_panel_widget, 1) codex/review-repository-code-hpjdax self.filters_panel_widget = self._build_filters_panel() @@ -472,7 +478,6 @@ def _build_scan_tab(self): codex/review-repository-code-kf47vu filters_panel_layout.addWidget(scroll, 1) filter_layout.addWidget(scroll, 1) - main quick_btn_layout = QHBoxLayout() select_all_btn = QPushButton("Selecionar Tudo") @@ -495,11 +500,6 @@ def _build_scan_tab(self): filter_layout.addWidget(self.filters_panel_widget, 1) filter_layout.addLayout(custom_layout) - main - - main - main - main scan_splitter.addWidget(filter_group) progress_group = QGroupBox("Progresso")