From: Danilo M. Date: Sun, 3 May 2026 08:42:30 +0000 (+0200) Subject: feat: git view with status, pull/push, and deleted article restore X-Git-Tag: v1.0~11 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=8263e7374d2c81ed0a64481a978d6e7157016b08;p=publisher.git feat: git view with status, pull/push, and deleted article restore --- diff --git a/ui/git_view.py b/ui/git_view.py new file mode 100644 index 0000000..2c759e8 --- /dev/null +++ b/ui/git_view.py @@ -0,0 +1,134 @@ +from __future__ import annotations +from pathlib import Path +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QPlainTextEdit, QTabWidget, QListWidget, QListWidgetItem, +) +from PyQt6.QtCore import pyqtSignal +from workers.git_worker import GitWorker +import subprocess + +class GitView(QWidget): + refresh_needed = pyqtSignal() + + def __init__(self, repo_path: Path, parent=None): + super().__init__(parent) + self._repo_path = repo_path + self._worker: GitWorker | None = None + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout(self) + + status_row = QHBoxLayout() + self._branch_label = QLabel("Branch: —") + self._branch_label.setStyleSheet("color:#a855f7;font-weight:bold;") + status_row.addWidget(self._branch_label) + status_row.addStretch() + refresh_btn = QPushButton("🔄 Refresh") + refresh_btn.clicked.connect(self.load_status) + status_row.addWidget(refresh_btn) + layout.addLayout(status_row) + + tabs = QTabWidget() + + status_tab = QWidget() + st_layout = QVBoxLayout(status_tab) + self._status_log = QPlainTextEdit() + self._status_log.setReadOnly(True) + self._status_log.setStyleSheet("background:#0a0a12;color:#ccc;font-family:monospace;font-size:11px;") + st_layout.addWidget(self._status_log) + + btns = QHBoxLayout() + for label, slot in [ + ("⬇ Pull", self._do_pull), + ("🧪 Push master", self._do_push_master), + ("🚢 Push production", self._do_push_production), + ]: + btn = QPushButton(label) + btn.clicked.connect(slot) + btns.addWidget(btn) + btns.addStretch() + st_layout.addLayout(btns) + tabs.addTab(status_tab, "Stato repo") + + deleted_tab = QWidget() + del_layout = QVBoxLayout(deleted_tab) + del_layout.addWidget(QLabel("Articoli eliminati (da git log):")) + self._deleted_list = QListWidget() + del_layout.addWidget(self._deleted_list) + restore_btn = QPushButton("♻️ Ripristina selezionato") + restore_btn.clicked.connect(self._do_restore) + del_layout.addWidget(restore_btn) + tabs.addTab(deleted_tab, "Articoli eliminati") + + layout.addWidget(tabs) + + self._output = QPlainTextEdit() + self._output.setReadOnly(True) + self._output.setStyleSheet("background:#0a0a12;color:#00ff88;font-family:monospace;font-size:11px;") + self._output.setMaximumHeight(150) + layout.addWidget(self._output) + + def load_status(self): + try: + branch = subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + cwd=self._repo_path, text=True, + ).strip() + self._branch_label.setText(f"Branch: {branch}") + + status = subprocess.check_output( + ["git", "status", "--short"], + cwd=self._repo_path, text=True, + ) + log = subprocess.check_output( + ["git", "log", "--oneline", "-5"], + cwd=self._repo_path, text=True, + ) + self._status_log.setPlainText(f"--- git status ---\n{status}\n--- ultimi commit ---\n{log}") + + self._deleted_list.clear() + deleted = subprocess.check_output( + ["git", "log", "--diff-filter=D", "--name-only", "--pretty=format:%H %s", "--", "content/"], + cwd=self._repo_path, text=True, + ) + current_hash = None + for line in deleted.splitlines(): + if line.strip() == "": + continue + if line.startswith("content/"): + if current_hash: + item = QListWidgetItem(f"{line} [{current_hash[:8]}]") + item.setData(256, (current_hash, line)) + self._deleted_list.addItem(item) + else: + parts = line.split(" ", 1) + current_hash = parts[0] if parts else None + except Exception as e: + self._output.appendPlainText(f"[Errore] {e}") + + def _run_worker(self, worker): + self._worker = worker + worker.output.connect(self._output.appendPlainText) + worker.error.connect(lambda e: self._output.appendPlainText(f"[ERRORE] {e}")) + worker.finished.connect(lambda ok: self.refresh_needed.emit() if ok else None) + worker.start() + + def _do_pull(self): + self._run_worker(GitWorker.pull(self._repo_path, self)) + + def _do_push_master(self): + self._run_worker(GitWorker.push_master(self._repo_path, self)) + + def _do_push_production(self): + self._run_worker(GitWorker.push_production(self._repo_path, self)) + + def _do_restore(self): + item = self._deleted_list.currentItem() + if not item: + return + commit_hash, rel_path = item.data(256) + worker = GitWorker.restore_article(self._repo_path, commit_hash, rel_path, self) + self._run_worker(worker) + self.load_status() diff --git a/ui/main_window.py b/ui/main_window.py index 4073591..ad7134f 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -91,8 +91,15 @@ class MainWindow(QMainWindow): self._stack.addWidget(self._translation_view) self._page_translations = self._stack.count() - 1 + from ui.git_view import GitView + + self._git_view = GitView(Path(self.config.blog_repo), parent=self) + self._git_view.refresh_needed.connect(self._refresh_articles) + self._stack.addWidget(self._git_view) + self._page_git = self._stack.count() - 1 + # Remaining placeholders - for name in ["taxonomy", "media", "git", "hugo"]: + for name in ["taxonomy", "media", "hugo"]: w = QLabel(f"[{name}]") w.setAlignment(Qt.AlignmentFlag.AlignCenter) self._stack.addWidget(w) @@ -205,10 +212,31 @@ class MainWindow(QMainWindow): self._stack.setCurrentIndex(self._page_translations) def _do_git_push(self, branch: str): - pass # implemented in Task 16 + from workers.git_worker import GitWorker + repo = Path(self.config.blog_repo) + if branch == "master": + worker = GitWorker.push_master(repo, self) + else: + worker = GitWorker.push_production(repo, self) + worker.error.connect(lambda e: self.statusBar().showMessage(f"Git error: {e}", 5000)) + worker.finished.connect(lambda ok: self.statusBar().showMessage("Push completato." if ok else "Push fallito.", 4000)) + worker.start() def _do_delete_article(self, article: Article): - pass # implemented in Task 16 + from PyQt6.QtWidgets import QMessageBox + from workers.git_worker import GitWorker + reply = QMessageBox.question( + self, "Elimina articolo", + f"Eliminare '{article.slug}' ({article.lang})? L'operazione sarà tracciata in git.", + ) + if reply == QMessageBox.StandardButton.Yes: + rel = str(article.path.parent.relative_to(Path(self.config.blog_repo))) + worker = GitWorker.remove_article( + Path(self.config.blog_repo), rel, article.slug, article.lang, self + ) + worker.error.connect(lambda e: self.statusBar().showMessage(f"Errore: {e}", 5000)) + worker.finished.connect(lambda ok: self._refresh_articles() if ok else None) + worker.start() def _setup_watcher(self): if not self.config.blog_repo: