Files
auto-git/systray.py
2026-03-06 18:57:01 +01:00

153 lines
5.6 KiB
Python

import os
import time
import threading
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from git import Repo
import pathspec
from plyer import notification
from pystray import Icon, Menu, MenuItem
from PIL import Image, ImageDraw
import logging
# Logi będą zapisywane w tym samym folderze co skrypt
log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "debug_log.txt")
logging.basicConfig(
filename=log_path,
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# --- TWOJA KLASA GIT (Z POPRZEDNICH KROKÓW) ---
class GitAutoCommitHandler(FileSystemEventHandler):
def __init__(self, repo_path):
self.repo_path = os.path.abspath(repo_path)
self.repo = Repo(self.repo_path)
self.spec = self._load_gitignore()
self.last_committed = {}
self.debounce_seconds = 3.0
def _load_gitignore(self):
gitignore_path = os.path.join(self.repo_path, '.gitignore')
if os.path.exists(gitignore_path):
with open(gitignore_path, 'r') as f:
return pathspec.PathSpec.from_lines('gitwildmatch', f)
return None
def _is_ignored(self, file_path):
abs_file_path = os.path.abspath(file_path)
relative_path = os.path.relpath(abs_file_path, self.repo_path)
if ".git" in relative_path.split(os.sep):
logging.debug(f"Ignorowanie (folder .git): {relative_path}")
return True
return self.spec.match_file(relative_path) if self.spec else False
def on_created(self, event):
if not event.is_directory: self._process_event(event.src_path, "new-file")
def on_modified(self, event):
if not event.is_directory: self._process_event(event.src_path, "update-file")
def on_deleted(self, event):
if not event.is_directory: self._process_event(event.src_path, "remove-file")
def on_moved(self, event):
if not event.is_directory: self._process_rename(event.src_path, event.dest_path)
def _process_event(self, file_path, action):
abs_path = os.path.abspath(file_path)
if self._is_ignored(abs_path):
return
logging.debug(f"Zdarzenie: {action} - {file_path}")
now = time.time()
if now - self.last_committed.get(abs_path, 0) > self.debounce_seconds:
self.last_committed[abs_path] = now
self._commit(abs_path, action)
def _process_rename(self, old, new):
if self._is_ignored(new): return
rel_old, rel_new = os.path.relpath(old, self.repo_path), os.path.relpath(new, self.repo_path)
self.repo.index.remove([rel_old], working_tree=False)
self.repo.index.add([rel_new])
self._finalize_commit(f"rename-file: {rel_old} -> {rel_new}", rel_new)
def _commit(self, file_path, action):
logging.debug(f"Commitowanie: {action} - {file_path}")
rel_path = os.path.relpath(file_path, self.repo_path)
if action == "remove-file":
self.repo.index.remove([rel_path])
else:
self.repo.index.add([rel_path])
self._finalize_commit(f"{action}: {rel_path}", rel_path)
# def _finalize_commit(self, msg, display_name):
# if self.repo.index.diff("HEAD"):
# self.repo.index.commit(msg)
# # POWIADOMIENIE WINDOWS
# notification.notify(
# title="Git Auto-Commit",
# message=f"Zapisano zmianę: {display_name}",
# app_name="Git Watcher",
# timeout=3
# )
def _finalize_commit(self, msg, display_name):
try:
if self.repo.is_dirty(untracked_files=True): # Dokładniejsze sprawdzenie
self.repo.index.commit(msg)
logging.info(f"✅ COMMIT SUCCESS: {msg}")
# Próba powiadomienia z łapaniem błędów
try:
notification.notify(
title="Git Auto-Commit",
message=f"Zapisano: {display_name}",
timeout=5
)
except Exception as e:
logging.error(f"Błąd powiadomienia: {e}")
except Exception as e:
logging.error(f"Błąd finalizacji commitu: {e}")
# --- LOGIKA TRAY I URUCHAMIANIA ---
def create_image():
# Generuje prostą ikonę (niebieskie koło), jeśli nie masz pliku .ico
width, height = 64, 64
image = Image.new('RGB', (width, height), (255, 255, 255))
dc = ImageDraw.Draw(image)
dc.ellipse([10, 10, 54, 54], fill=(0, 122, 204))
return image
def run_watcher(path, stop_event):
event_handler = GitAutoCommitHandler(path)
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
while not stop_event.is_set():
time.sleep(1)
observer.stop()
observer.join()
def main():
logging.info("Aplikacja wystartowała")
repo_path = "./moje-repo" # Zmień na swoją ścieżkę
logging.info("Repozytorium: %s", repo_path)
stop_event = threading.Event()
# Uruchomienie obserwatora w osobnym wątku
watcher_thread = threading.Thread(target=run_watcher, args=(repo_path, stop_event))
watcher_thread.daemon = True
watcher_thread.start()
# Menu zasobnika
def on_quit(icon, item):
stop_event.set()
icon.stop()
icon = Icon("GitWatcher", create_image(), menu=Menu(
MenuItem(f"Śledzę: {os.path.abspath(repo_path)}", None, enabled=False),
MenuItem("Zakończ", on_quit)
))
icon.run()
if __name__ == "__main__":
main()