Add in-app self-update via git (Hilfe menu)

New Updater class + "Hilfe" menubar:
- "Nach Update suchen": git fetch + count of new upstream commits.
- Quiet background check on start; surfaces via status bar + menu label.
- Install: git merge --ff-only, pip install if requirements.txt changed,
  then restart via os.execv. data/ is git-ignored and untouched.
- Fast-forward only; diverged history or offline -> clear message, no action.
- Inert unless run from a git clone with git on PATH.

Also: "Version…" menu item shows the installed commit.

Verified against throwaway git repos (check / ff-update / diverged / no-op /
non-clone) and via GUI build on Python 3.14 / Tk 9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 22:06:51 +02:00
parent e3bbd4d264
commit 1bed723363
7 changed files with 330 additions and 10 deletions
+1 -1
View File
@@ -31,4 +31,4 @@ code) and plots them on an interactive map.
- **UI language:** German; DRK-red header, enlarged font, colour-coded status bar, window position remembered
- **Persistence:** atomic CSV writes + rotating backups in `data/backups/`; log at `data/app.log`
- **Tests:** non-GUI smoke checks only (not yet a committed `tests/` suite)
- **Version control:** git initialized 2026-09-07
- **Version control:** git initialized 2026-09-07; in-app self-update (`git` fast-forward + restart) via the Hilfe menu
+29 -1
View File
@@ -12,7 +12,8 @@ Everything lives in [`app.py`](../app.py). There is no package structure.
| `parse_date()` / `parse_coord()` / helpers | Module-level pure functions for normalizing user input (date → ISO, coordinate strings → float, `"City (PLZ)"` splitting). Shared by the entry tab and `EditDialog`. |
| `generate_map()` | Builds `karte.html` from `DataStore.get_map_data()` with Folium. |
| `EditDialog` | `tk.Toplevel` modal dialog for editing one row. Returns its result via `self.result`. |
| `App` | `ttkbootstrap.Window`. Builds the UI, owns the `DataStore`, the `GeocoderWorker`, and the in-memory `_queue` list for Tab 1. |
| `Updater` | Wraps `git` for self-update. `available` is true only from a git clone with `git` on `PATH`. `check()` → new-commit count (`fetch` + `rev-list`); `update()``fetch` + `merge --ff-only` + conditional `pip install`. All git calls are `subprocess.run` with timeouts. Raises `UpdateError` (user-facing German text) on any failure. |
| `App` | `ttkbootstrap.Window`. Builds the UI + menubar, owns the `DataStore`, the `GeocoderWorker`, the `Updater`, and the in-memory `_queue` list for Tab 1. |
## Data flow
@@ -71,6 +72,33 @@ user clicks "Alle speichern" -> _on_save_all()
app is open, the next in-app save overwrites those changes (but the pre-write
backup captures them).
## Self-update flow
```
App start ─► daemon thread: sleep 2s ─► Updater.check()
(fetch + count) └─ on error: log only, stay quiet
N > 0 ─► self.after(0, …) ─► status hint + menu label
+ "Update verfügbar?" dialog
Hilfe ▸ Nach Update suchen ─► worker thread ─► Updater.check()
error ─► warning dialog (offline?)
N = 0 ─► "aktuell" dialog
N > 0 ─► "jetzt installieren?" dialog
install ─► worker thread ─► Updater.update() (fetch, ff-only merge, pip)
success + changed ─► info dialog ─► _restart()
save window state,
stop geocoder,
os.chdir(BASE_DIR),
os.execv(python, [python, app.py])
ff-only fails / pip fails ─► error dialog, no restart
```
Everything network- or subprocess-bound runs off the UI thread; results are
marshalled back with `self.after(0, …)` (same rule as the geocoder).
## Logging
`_setup_logging()` (called from `__main__`) attaches a `RotatingFileHandler` to
+20
View File
@@ -2,6 +2,26 @@
Notable changes to the app. Newest first.
## 2026-09-07 — built-in self-update
New **Hilfe** menu:
- **Nach Update suchen** `git fetch` + count of new commits on the server.
- **Version…** shows the installed commit (`<hash> · <date>`).
On start, a quiet background check runs; if updates exist the status bar says so
and the menu item becomes "Update installieren (N verfügbar)".
Installing runs `git merge --ff-only` (fast-forward only never an automatic
merge), re-runs `pip install -r requirements.txt` if that file changed, then
restarts the app via `os.execv`. User data (`data/`) is git-ignored and
untouched. If the local checkout has diverged or the network is down, it shows a
clear message and does nothing.
Only active when the app runs from a **git clone** with `git` installed;
otherwise the menu just shows "Version: unbekannt". New `Updater` class,
`app.py`.
## 2026-09-07 — map tiles switched to OpenStreetMap
`folium.Map(tiles="CartoDB positron")` began showing an "API KEY REQUIRED"
+13 -3
View File
@@ -212,9 +212,19 @@ Second review, focused on the interface. Items 112 implemented; 13 deferred.
Full custom DRK-red *theme* (recolouring `primary` etc.) was **not** done — the
red header bar gives the branding without fighting ttkbootstrap's theme system.
## Self-update (2026-09-07) — ✅ done
**Hilfe ▸ Nach Update suchen** / quiet check on start / restart via `os.execv`.
`git merge --ff-only` only, `pip install` if `requirements.txt` changed, `data/`
untouched. Needs a git clone + `git` installed (`Updater.available`).
Open follow-ups: no rollback if a pushed update is broken (mitigated by
`--ff-only` from a branch Patrick controls + pip errors caught pre-restart);
`run.sh` / `.desktop` still not committed (item 5).
## Suggested order of remaining work
1. Initial git commit (item 4), then decide on committing `orte.csv`.
2. Pinned deps + `run.sh` / `.desktop` launcher in the repo (item 5).
3. Stable `id` column (item 7 of the first review).
4. Formalize tests (item 10); offline map (item 9) only if offline use becomes real.
2. **Set up the git remote** the updater pulls from, and pin all deps (item 5).
3. `run.sh` + `.desktop` launcher in the repo (item 5).
4. Stable `id` column (item 7 of the first review).
5. Formalize tests (item 10); offline map (item 9) only if offline use becomes real.
+3
View File
@@ -1,5 +1,8 @@
# Overview what the app does
There is a **Hilfe** menu with "Nach Update suchen" (self-update via `git`, see
[setup.md](setup.md#updating)) and "Version…".
The app is a single window: a **DRK-red header bar**, two tabs, and a
**colour-coded status bar** at the bottom (grey = neutral, green = success,
orange = warning, red = error; transient messages fade back to neutral after
+26 -5
View File
@@ -7,16 +7,24 @@ during install.
## 1. System packages
Tkinter is not bundled with the system Python on Mint and must be installed
separately:
separately. `git` is needed for the in-app updater:
```bash
sudo apt update
sudo apt install python3-tk python3-venv python3-pip
sudo apt install python3-tk python3-venv python3-pip git
```
## 2. Get the code
Put the project folder somewhere stable, e.g. `~/Apps/DRK_Blutspende_Orte`.
**Clone it** (don't download a zip) the in-app "Nach Update suchen" only works
from a git clone:
```bash
mkdir -p ~/Apps && cd ~/Apps
git clone <repo-url> DRK_Blutspende_Orte
```
Put it somewhere stable, e.g. `~/Apps/DRK_Blutspende_Orte`.
## 3. Create a virtual environment and install dependencies
@@ -94,13 +102,26 @@ that. The macOS CommandLineTools Python used during development ships Tk 8.5 and
## Updating
**From inside the app:** menu **Hilfe ▸ Nach Update suchen**. The app also
checks quietly on start and offers the update if there is one. It fast-forwards
to the server version, reinstalls dependencies if `requirements.txt` changed,
and restarts itself. `data/` is never touched.
For this to work the app must run from a **git clone** (step 2) whose `.venv`
was made with the same Python it runs on, and `git` must be installed. The
updater only ever fast-forwards if the local copy has been changed by hand it
refuses and says to get in touch.
**Manually** (equivalent):
```bash
cd ~/Apps/DRK_Blutspende_Orte
git pull # once the project is in git
git pull --ff-only
.venv/bin/pip install -r requirements.txt
```
## Data location
All state is in `data/orte.csv` next to `app.py`. To back up the app, copy that
one file. To move to a new laptop, copy the whole folder and redo steps 1 & 3.
one file (plus `data/backups/` if you want the history). To move to a new
laptop, clone the repo again and redo steps 1 & 3; copy `data/orte.csv` across.