]> danix's work - publisher.git/commitdiff
feat: git view with status, pull/push, and deleted article restore
authorDanilo M. <redacted>
Sun, 3 May 2026 08:42:30 +0000 (10:42 +0200)
committerDanilo M. <redacted>
Sun, 3 May 2026 08:42:30 +0000 (10:42 +0200)
ui/git_view.py [new file with mode: 0644]
ui/main_window.py

diff --git a/ui/git_view.py b/ui/git_view.py
new file mode 100644 (file)
index 0000000..2c759e8
--- /dev/null
@@ -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()
index 407359124c487b5eefa43254f2bac3b4078a2240..ad7134fa33b3c3c791f7688160ed3b91a976fbd5 100644 (file)
@@ -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: