diff --git a/gemini.md b/gemini.md index 54194e2..2a62d25 100644 --- a/gemini.md +++ b/gemini.md @@ -286,7 +286,7 @@ Responsible for Windows notifications. Recommended library: ``` -win10toast +win10toast or plyer ``` Main class: diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..f2b8e55 --- /dev/null +++ b/logger.py @@ -0,0 +1,27 @@ +import logging +import os +import sys + +def setup_logger(): + log_file = "git_monitor.log" + # Ensure log is in the application directory + log_path = os.path.abspath(log_file) + + logger = logging.getLogger("GitMonitor") + logger.setLevel(logging.INFO) + + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + + # File handler + file_handler = logging.FileHandler(log_path) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Also log to stdout for development (though it will be --noconsole eventually) + stream_handler = logging.StreamHandler(sys.stdout) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + + return logger + +logger = setup_logger() diff --git a/main.py b/main.py deleted file mode 100644 index 8bd5b3f..0000000 --- a/main.py +++ /dev/null @@ -1,135 +0,0 @@ -import time -import os -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler -from git import Repo - -# KONFIGURACJA -PATH_TO_REPO = './moje-repo' # Ścieżka do Twojego katalogu git -COMMIT_MESSAGE = "Automatyczny commit: zmiana w pliku" - -import pathspec -from watchdog.events import FileSystemEventHandler - - -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() - - # Słownik do przechowywania czasu ostatniego commitu dla każdego pliku - self.last_committed = {} - self.debounce_seconds = 0.5 # Minimalny odstęp między commitami dla jednego pliku - - 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: - # Tworzymy wzorzec dopasowania na podstawie linii w pliku - return pathspec.PathSpec.from_lines('gitwildmatch', f) - return None - - def _is_ignored(self, file_path): - # Sprawdzamy ścieżkę względem głównego folderu repozytorium - relative_path = os.path.relpath(file_path, self.repo_path) - - # Zawsze ignoruj folder .git - if ".git" in relative_path.split(os.sep): - return True - - if self.spec and self.spec.match_file(relative_path): - return True - return False - - def on_modified(self, event): - if not event.is_directory and not self._is_ignored(event.src_path): - self._process_event(event.src_path, "update-file") - - def on_created(self, event): - if not event.is_directory and not self._is_ignored(event.src_path): - self._process_event(event.src_path, "new-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: - # Sprawdzamy, czy nowa lokalizacja nie jest ignorowana - if self._is_ignored(event.dest_path): - # Jeśli przenieśliśmy plik do folderu ignorowanego, traktujemy to jak usunięcie - self._process_event(event.src_path, "remove-file") - else: - self._process_rename(event.src_path, event.dest_path) - - def _process_event(self, file_path, action_label): - if self._is_ignored(file_path): - return - - current_time = time.time() - last_time = self.last_committed.get(file_path, 0) - - # Sprawdź, czy minęło dość czasu od ostatniego commitu tego pliku - if current_time - last_time > self.debounce_seconds: - self.last_committed[file_path] = current_time - self._commit(file_path, action_label) - - def _process_rename(self, old_path, new_path): - current_time = time.time() - # Używamy nowej ścieżki jako klucza do debounce - last_time = self.last_committed.get(new_path, 0) - - if current_time - last_time > self.debounce_seconds: - self.last_committed[new_path] = current_time - - try: - old_rel = os.path.relpath(old_path, self.repo_path) - new_rel = os.path.relpath(new_path, self.repo_path) - - # Git sam wykrywa rename, jeśli dodamy oba pliki (usunięty i nowy) - # Ale możemy to zrobić jawnie dla czystości: - self.repo.index.remove([old_rel], working_tree=False) - self.repo.index.add([new_rel]) - - if self.repo.index.diff("HEAD"): - self.repo.index.commit(f"rename-file: {old_rel} -> {new_rel}") - print(f"🔄 RENAME: {old_rel} -> {new_rel}") - except Exception as e: - print(f"⚠️ Błąd podczas zmiany nazwy: {e}") - - def _commit(self, file_path, action_label): - try: - relative_path = os.path.relpath(file_path, self.repo_path) - - if action_label == "remove-file": - self.repo.index.remove([relative_path]) - else: - self.repo.index.add([relative_path]) - if self.repo.index.diff("HEAD"): - self.repo.index.commit(f"{action_label}: {relative_path}") - print(f"{action_label}: {relative_path}") - # if self.repo.is_dirty(path=relative_path) or relative_path in self.repo.untracked_files: - # self.repo.index.add([relative_path]) - # self.repo.index.commit(f"Auto-commit: {relative_path}") - # print(f"✅ Zapisano: {relative_path}") - except Exception as e: - print(f"❌ Błąd: {e}") - - -if __name__ == "__main__": - if not os.path.exists(os.path.join(PATH_TO_REPO, ".git")): - print("Błąd: Wskazany katalog nie jest repozytorium Gita!") - else: - event_handler = GitAutoCommitHandler(PATH_TO_REPO) - observer = Observer() - observer.schedule(event_handler, PATH_TO_REPO, recursive=True) - - print(f"🚀 Śledzenie katalogu: {PATH_TO_REPO}...") - observer.start() - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - observer.stop() - observer.join() \ No newline at end of file diff --git a/systray.py b/systray.py deleted file mode 100644 index 0b29137..0000000 --- a/systray.py +++ /dev/null @@ -1,153 +0,0 @@ -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() \ No newline at end of file