From bc868f5af1d367cdfcdb14187bc0f3ff81d5e4f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 22:30:42 +0300 Subject: [PATCH] =?UTF-8?q?Git-=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B2=D1=81=D0=B5=D0=B3=D0=B4=D0=B0=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B4=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=BE=D0=B9=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85,=20=D0=B0=20=D0=BD=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B4=20=D0=BA=D0=BE=D1=80=D0=BD=D0=B5=D0=BC=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Приложение создавало GitSync от paths.app_root(). Пока база лежала внутри репозитория с кодом, это работало; после разделения репозиториев — нет. exe лежит в корне клона с исходниками, поэтому приложение распоряжалось репозиторием кода: переписало ему origin на food-records и попыталось отправить туда исходники. Пуш спас только отказ по fast-forward. Теперь GitSync создаётся в одном месте — MainWindow._git() — и всегда от paths.data_dir(). Тест проверяет и цель, и то, что других мест создания GitSync в файле не осталось. Co-Authored-By: Claude Opus 5 --- app/ui/main_window.py | 20 ++++++++++++++------ tests/test_ui_smoke.py | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 5a9a17b..f05498c 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -168,6 +168,16 @@ class MainWindow(QMainWindow): def sync_status_text(self) -> str: 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: @@ -214,7 +224,7 @@ class MainWindow(QMainWindow): self._sync_text = "синхронизация…" 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.done.connect(lambda *args: self._on_sync_done(*args, quiet=quiet)) self._sync_task.start() @@ -267,8 +277,7 @@ class MainWindow(QMainWindow): # --- разрешение конфликта --- def adopt_remote_version(self, _copy_path: Path) -> None: - git = self.vault.doc.settings.git - sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git) + sync = self._git() try: sync.reset_to_remote() self.vault.reload() @@ -289,9 +298,8 @@ class MainWindow(QMainWindow): self.changed() def force_push_local(self) -> None: - git = self.vault.doc.settings.git self.save_now() - sync = GitSync(paths.app_root(), paths.vault_rel_posix(), git) + sync = self._git() try: sync.force_push() except GitError as exc: @@ -325,7 +333,7 @@ class MainWindow(QMainWindow): # Последний пуш на выходе, с потолком по времени: висеть на закрытии # из-за пропавшей сети приложение не должно. 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.start() task.wait(CLOSE_SYNC_WAIT_MS) diff --git a/tests/test_ui_smoke.py b/tests/test_ui_smoke.py index 1375953..bb69aad 100644 --- a/tests/test_ui_smoke.py +++ b/tests/test_ui_smoke.py @@ -107,6 +107,32 @@ def test_sync_is_skipped_without_a_remote(window): 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 + + # --- таблицы наполняются --------------------------------------------------