Git-операции всегда над папкой данных, а не над корнем приложения

Приложение создавало GitSync от paths.app_root(). Пока база лежала внутри
репозитория с кодом, это работало; после разделения репозиториев — нет.
exe лежит в корне клона с исходниками, поэтому приложение распоряжалось
репозиторием кода: переписало ему origin на food-records и попыталось
отправить туда исходники. Пуш спас только отказ по fast-forward.

Теперь GitSync создаётся в одном месте — MainWindow._git() — и всегда
от paths.data_dir(). Тест проверяет и цель, и то, что других мест
создания GitSync в файле не осталось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-08-22 22:30:42 +03:00
parent 1a97fee872
commit bc868f5af1
2 changed files with 40 additions and 6 deletions

View File

@ -168,6 +168,16 @@ class MainWindow(QMainWindow):
def sync_status_text(self) -> str: def sync_status_text(self) -> str:
return self._sync_text return self._sync_text
def _git(self) -> GitSync:
"""Git всегда работает над папкой данных, а не над корнем приложения.
Единственная точка создания GitSync: если exe лежит внутри клона
с исходниками, ошибка в этом месте заставила бы приложение
распоряжаться репозиторием кода переписать ему origin и запушить
исходники в репозиторий данных.
"""
return GitSync(paths.data_dir(), paths.vault_rel_posix(), self.vault.doc.settings.git)
# --- сохранение --- # --- сохранение ---
def _autosave(self) -> None: def _autosave(self) -> None:
@ -214,7 +224,7 @@ class MainWindow(QMainWindow):
self._sync_text = "синхронизация…" self._sync_text = "синхронизация…"
self._update_status() self._update_status()
sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git) sync = self._git()
self._sync_task = SyncTask(sync, paths.remote_copy_path(), self) self._sync_task = SyncTask(sync, paths.remote_copy_path(), self)
self._sync_task.done.connect(lambda *args: self._on_sync_done(*args, quiet=quiet)) self._sync_task.done.connect(lambda *args: self._on_sync_done(*args, quiet=quiet))
self._sync_task.start() self._sync_task.start()
@ -267,8 +277,7 @@ class MainWindow(QMainWindow):
# --- разрешение конфликта --- # --- разрешение конфликта ---
def adopt_remote_version(self, _copy_path: Path) -> None: def adopt_remote_version(self, _copy_path: Path) -> None:
git = self.vault.doc.settings.git sync = self._git()
sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git)
try: try:
sync.reset_to_remote() sync.reset_to_remote()
self.vault.reload() self.vault.reload()
@ -289,9 +298,8 @@ class MainWindow(QMainWindow):
self.changed() self.changed()
def force_push_local(self) -> None: def force_push_local(self) -> None:
git = self.vault.doc.settings.git
self.save_now() self.save_now()
sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git) sync = self._git()
try: try:
sync.force_push() sync.force_push()
except GitError as exc: except GitError as exc:
@ -325,7 +333,7 @@ class MainWindow(QMainWindow):
# Последний пуш на выходе, с потолком по времени: висеть на закрытии # Последний пуш на выходе, с потолком по времени: висеть на закрытии
# из-за пропавшей сети приложение не должно. # из-за пропавшей сети приложение не должно.
self.status_label.setText("Отправляю базу на сервер…") self.status_label.setText("Отправляю базу на сервер…")
sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git) sync = self._git()
task = SyncTask(sync, paths.remote_copy_path(), self) task = SyncTask(sync, paths.remote_copy_path(), self)
task.start() task.start()
task.wait(CLOSE_SYNC_WAIT_MS) task.wait(CLOSE_SYNC_WAIT_MS)

View File

@ -107,6 +107,32 @@ def test_sync_is_skipped_without_a_remote(window):
assert window._syncing is False assert window._syncing is False
def test_git_always_targets_the_data_folder(window):
"""Приложение не должно распоряжаться репозиторием с исходниками.
exe обычно лежит в корне клона с кодом, и папка data внутри него.
Если git начать с корня приложения, оно перепишет исходникам origin
и запушит их в репозиторий данных. Ровно это однажды и произошло.
"""
from app import paths
sync = window._git()
assert sync.repo_dir == paths.data_dir()
assert sync.repo_dir != paths.app_root()
assert sync.rel_path == "vault.fmdb"
assert "/" not in sync.rel_path # база лежит в корне своего репозитория
def test_no_other_place_builds_git_by_hand():
"""Единственная точка создания GitSync — MainWindow._git()."""
from pathlib import Path
source = Path("app/ui/main_window.py").read_text(encoding="utf-8")
# Одно вхождение — внутри самого _git(); остальные зовут его.
assert source.count("GitSync(") == 1
assert "paths.app_root()" not in source
# --- таблицы наполняются -------------------------------------------------- # --- таблицы наполняются --------------------------------------------------