chore: initial structure and logger.py

This commit is contained in:
2026-03-06 19:19:09 +01:00
parent d760fa81bc
commit 8753a25cc6
4 changed files with 28 additions and 289 deletions

View File

@@ -286,7 +286,7 @@ Responsible for Windows notifications.
Recommended library:
```
win10toast
win10toast or plyer
```
Main class:

27
logger.py Normal file
View File

@@ -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()

135
main.py
View File

@@ -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()

View File

@@ -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()