--- /dev/null
+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()
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)
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: