RELEASING.md 30,1 КБ
Newer Older
Nikitos's avatar
Nikitos включено в состав коммита
1
2
3
# Выпуск релиза

Как выпускать Russiano2D: что проверить, как собрать, упаковать, подписать
Nikitos's avatar
Nikitos включено в состав коммита
4
контрольными суммами и опубликовать сразу на **два** хостинга.
Nikitos's avatar
Nikitos включено в состав коммита
5
6
7
8
9

Два хоста — две роли:

| Хост | Remote | Доступ | Роль |
|---|---|---|---|
Nikitos's avatar
Nikitos включено в состав коммита
10
11
| **hub.mos.ru** | `origin`, первая push-цель | **публичный** | основной: исходники и вся история |
| **gitverse.ru** | `origin`, вторая push-цель, и `gitverse` | публичный | зеркало кода |
Nikitos's avatar
Nikitos включено в состав коммита
12

Nikitos's avatar
Nikitos включено в состав коммита
13
14
15
16
17
18
19
* ссылки в README, ссылки в документации и бейджи ведут на hub.mos.ru — он основной;
* **релиз — это `dist/` в репозитории**, он раздаётся оттуда на обоих хостах,
  отдельных «релизов» с выгрузкой нет (раздел 6);
* собирает и публикует локальный `build_and_push.sh`: поднимает версию,
  собирает все платформы, пушит ветку и тег `vX.Y.Z` (раздел 5.6);
* пайплайн на GitVerse написан, но **выключен** (раздел 5.1);
* GitLab-пайплайн на hub.mos.ru убран — раннера там не было (раздел 5.1).
Nikitos's avatar
Nikitos включено в состав коммита
20
21
22
23
24
25
26
27
28

---

## 0. Блокеры публичного релиза

Пока не закрыты эти пункты, публичный релиз выпускать нельзя.

### 0.1. Лицензия выбрана (закрыто)

Nikitos's avatar
Nikitos включено в состав коммита
29
30
В корне лежит `LICENSE` — авторская лицензия: движок можно использовать,
менять, распространять и продавать без отчислений; игры, сделанные на нём,
Nikitos's avatar
Nikitos включено в состав коммита
31
32
33
распространяются свободно. В лицензии прямо сказано, что движок русский и
делает его русский автор: если это не нравится — движком не пользуются.
Уведомления зависимостей сохраняются отдельно, в `THIRD_PARTY_NOTICES.md`.
Nikitos's avatar
Nikitos включено в состав коммита
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

`tools/release.py` в настоящем (не `--dry-run`) режиме требует наличия
`LICENSE` — теперь файл есть, блокер снят. Карта вариантов осталась в
разделе 3 как справка: если владелец решит сменить лицензию на стандартную
(MIT, Apache-2.0 и т. п.), порядок действий там же.

### 0.2. Права на демо-ассеты не подтверждены

`demos/assets/art/**` собраны `tools/make_demo_assets.py` из исходников,
лежащих **вне** репозитория (`R2D_SOURCE_ART`), а происхождение
`assets/audio/**` в репозитории не зафиксировано. До релиза владелец должен
подтвердить права либо исключить эти файлы из поставки. Подробнее —
`THIRD_PARTY_NOTICES.md`, раздел «Ассеты».

### 0.3. Путь до репозитория уточнён (закрыто)

Nikitos's avatar
Nikitos включено в состав коммита
50
51
Проект живёт по адресу <https://hub.mos.ru/dem4ev48/russiano2d>, зеркало —
<https://gitverse.ru/Nikide/russiano2d>. Remote'ы настроены так:
Nikitos's avatar
Nikitos включено в состав коммита
52
53
54

```bash
git remote -v
Nikitos's avatar
Nikitos включено в состав коммита
55
56
57
58
# origin    git@hub.mos.ru:dem4ev48/russiano2d.git  (fetch)
# origin    git@hub.mos.ru:dem4ev48/russiano2d.git  (push)
# origin    git@gitverse.ru:Nikide/russiano2d.git   (push)
# gitverse  git@gitverse.ru:Nikide/russiano2d.git
Nikitos's avatar
Nikitos включено в состав коммита
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
```

---

## 1. Версия

Версия живёт в одном месте — `project(... VERSION x.y.z ...)` в
`CMakeLists.txt`. Скрипт релиза читает и меняет именно её.

Схема — семантическое версионирование:

| Что изменилось | Какую цифру поднимать |
|---|---|
| ломающее изменение API | `MAJOR` (0.x — пока не обещаем стабильность) |
| новая возможность | `MINOR` |
| исправление | `PATCH` |

Nikitos's avatar
Nikitos включено в состав коммита
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Поднять патч-версию на единицу, ничего не собирая:

```bash
python3 tools/release.py --bump          # 0.1.0 → 0.1.1, печатает новую версию
python3 tools/release.py --bump --dry-run  # посмотреть, ничего не меняя
```

В stdout уходит только номер (`0.1.1`) — его удобно подхватывать в скриптах.
Локальный `build_and_push.sh` делает это сам в начале каждого запуска
(`BUMP=0` отключает), причём **до** сборки: иначе пакеты и README в `dist/`
соберутся со старым номером. Сам тег скрипт не ставит — тег запускает релиз
на GitVerse:

```bash
git push origin v0.1.1
```

Nikitos's avatar
Nikitos включено в состав коммита
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Одновременно дополнить `CHANGELOG.md`: записи из `[Unreleased]` переезжают в
раздел новой версии с датой.

---

## 2. Сборка и тесты

### 2.1. Debug — для проверок локально

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
```

### 2.2. Release — для поставки

```bash
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release -j
```

Для сборки скриптов внутрь бинарника (байткод QuickJS):

```bash
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release \
      -DR2D_EMBED_SCRIPTS=ON -DR2D_EMBED_DIR=demos
```

Полезные переключатели: `R2D_ENABLE_IMGUI`, `R2D_ENABLE_RMLUI`,
`R2D_ENABLE_AUDIO`, `R2D_ENABLE_HOTRELOAD`, `R2D_SANITIZE`.

### 2.3. Что прогоняется

| Набор | Чем | Нужен GPU |
|---|---|---|
| C-тесты JSON и ChaCha20-Poly1305 | `build/tests/r2d_json_test`, `build/tests/r2d_crypto_test` | нет |
| Юнит-тесты логики `$` | `qjs tests/js/*_test.mjs` (см. `docs/AGENT_API.md`) | нет |
Nikitos's avatar
ф    
Nikitos включено в состав коммита
130
| Публикация релиза из `dist/` | `.gitlab-ci.yml` (hub.mos.ru) и `.gitverse/workflows/publish-dist.yaml` — только выгрузка файлов, без сборки | секрет `RELEASE_API_KEY` нужен только на GitVerse |
Nikitos's avatar
Nikitos включено в состав коммита
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
| Агентские тесты движка | `python3 tools/run_tests.py` (`--fast` — быстрый набор) | **да** |
| Сборка игры в один файл | `tests/agent/build_test.py` | **да** |

Полный прогон:

```bash
python3 tools/run_tests.py
```

Быстрый (около минуты) и понятный локально:

```bash
python3 tools/run_tests.py --fast
```

Важно: раннер различает `ok`, `fail` и `skip`: пропуск (нет дисплея, нет
ассета) **не считается успехом**. Если релизная проверка дала `skip`, это
надо объяснить, а не «зачесть».

---

## 3. Выбор лицензии (справка)

Nikitos's avatar
Nikitos включено в состав коммита
154
155
Лицензия уже выбрана и лежит в `LICENSE` — авторская: движок можно
использовать и продавать без отчислений, игры распространять свободно. Она
Nikitos's avatar
Nikitos включено в состав коммита
156
157
158
не «максимально либеральная»: в ней прямо сказано, что движок русский и делает
его русский автор, и что тем, кому этот факт не нравится, пользоваться им не
стоит. Раздел оставлен на случай, если владелец захочет перейти
Nikitos's avatar
Nikitos включено в состав коммита
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
на стандартную лицензию. Важное
обстоятельство: движок **статически линкует** MIT/Apache/zlib-зависимости
(см. `THIRD_PARTY_NOTICES.md`), ни одна из них не требует открывать код
проекта.

| Кандидат | Плюсы | Минусы |
|---|---|---|
| **MIT** | максимально коротко и привычно для игровых движков; совместима со всеми зависимостями; легко читается | нет явного патентного гранта; нет условия делиться улучшениями |
| **Apache-2.0** | явный патентный грант и защита от патентных исков; хорошо для корпоративного использования | длиннее и «юридичнее»; требует сохранять NOTICE; несовместима с GPLv2 |
| **BSD-2/3-Clause** | почти как MIT, вариант с запретом использовать имя проекта в рекламе | патентный вопрос тоже не покрыт |
| **zlib** | как у SDL; очень либеральна, коротка | мало кто читает её как «бренд»; патентного гранта нет |
| **MPL-2.0** | файловый копилефт: правки самих файлов открываются, остальное можно закрывать | сложнее для пользователей; не «просто игру возьми» |
| **GPL-2.0/3.0** | гарантирует открытость производных | несовместима с проприетарными играми на движке; закрывает коммерческие форки |
| **Проприетарная / «все права защищены»** | полный контроль | публичный репозиторий становится почти бесполезен для сообщества; часть зависимостей всё равно требует уведомлений |
| **Двойная (например, MIT + коммерческая)** | открытость для сообщества и платный вариант для компаний | нужен CLA и юридическая работа; пугает часть контрибьюторов |

Что учесть при выборе:

* zlib/MIT/Apache-2.0/BSD — «отпускают» код, но требуют сохранить уведомления
  зависимостей (они уже собраны в `THIRD_PARTY_NOTICES.md`).
* Если хочется, чтобы улучшения движка возвращались в общий код — MPL-2.0 или
  GPL, но это ограничит использование в проприетарных играх.
* Шрифты (SIL OFL) и ассеты — отдельные лицензии, лицензия кода их не
  заменяет.

После выбора:

1. положить полный текст в `LICENSE` в корне;
2. добавить строку о лицензии в `README.md` (правит владелец);
3. добавить `LICENSE` в поставку — `tools/release.py` копирует его
   автоматически, если файл есть.

---

## 4. Упаковка

```bash
python3 tools/release.py --dry-run          # план, ничего не меняется
python3 tools/release.py --version 0.2.0    # реальный выпуск
```

Скрипт:

1. поднимает версию в `CMakeLists.txt` (если задан `--version`);
2. собирает Release в `build-release/`;
3. гоняет C-тесты и qjs-тесты;
4. раскладывает `dist/<os>-<arch>/`: бинарник, `assets/`, `game/`,
   `README.md`, `CHANGELOG.md`, `THIRD_PARTY_NOTICES.md`, `LICENSE` (если есть);
5. пишет `SHA256SUMS.txt` внутри пакета и сводный `dist/SHA256SUMS.txt`
   по архивам;
6. делает архивы `.tar.gz` (Linux/macOS) или `.zip` (Windows);
7. ставит аннотированный тег `vX.Y.Z`.

Полезные ключи:

| Ключ | Смысл |
|---|---|
| `--dry-run`, `-n` | печатает план, не пишет и не собирает |
| `--yes` | не спрашивать подтверждение (для скриптов) |
| `--platform linux-x86_64,windows-x86_64` | какие платформы упаковать |
| `--build-dir DIR` | каталог сборки (по умолчанию `build-release`) |
| `--out DIR` | каталог артефактов (по умолчанию `dist`) |
| `--with-demos` | положить в пакет ещё и `demos/` |
| `--force` | пересобрать непустой `dist/<os>-<arch>/` |
| `--skip-build` / `--skip-tests` / `--skip-tag` | осознанно пропустить шаг |

Проверка контрольных сумм перед публикацией:

```bash
cd dist && shasum -a 256 -c SHA256SUMS.txt
```

**Push и теги скрипт не делает.** Их публикует человек (раздел 6) — чтобы
случайный запуск не отправил недоделанный релиз в оба хоста.

---

## 5. CI/CD

Nikitos's avatar
Nikitos включено в состав коммита
238
239
240
241
242
> **CI выключен.** Релизы собираются и публикуются локально:
> `build_and_push.sh` поднимает версию, собирает все платформы, коммитит,
> пушит ветку и тег `vX.Y.Z` на оба хостинга. Готовые сборки лежат в `dist/`
> репозитория — отдельной выгрузки релизов нет (раздел 6).

Nikitos's avatar
Nikitos включено в состав коммита
243
244
### 5.1. Что где стоит

Nikitos's avatar
Nikitos включено в состав коммита
245
| | gitverse.ru | hub.mos.ru |
Nikitos's avatar
Nikitos включено в состав коммита
246
|---|---|---|
Nikitos's avatar
Nikitos включено в состав коммита
247
248
249
250
| Файл | `.gitverse/workflows/release.yaml` | — |
| Роль | пайплайн написан, но **выключен** | CI нет |
| Когда | только ручной запуск из вкладки CI/CD | — |
| Платформы | Linux x86_64 (нативно) + Windows x86_64 (MinGW) | — |
Nikitos's avatar
Nikitos включено в состав коммита
251

Nikitos's avatar
Nikitos включено в состав коммита
252
253
254
255
У пайплайна **нет автоматического триггера** — только `workflow_dispatch`,
чтобы случайный пуш тега ничего не запускал. Как включить обратно, написано
в шапке самого файла: вернуть триггер по тегу и завести секрет
`RELEASE_API_KEY`.
Nikitos's avatar
Nikitos включено в состав коммита
256

Nikitos's avatar
Nikitos включено в состав коммита
257
258
259
GitLab-пайплайн с hub.mos.ru убран целиком: `.gitlab-ci.yml` и `docs/RUNNER.md`
удалены. Раннера там не было, WAF отдавал `403` на запросы с наших машин —
пайплайн проходил линтер, но не собирал ничего.
Nikitos's avatar
Nikitos включено в состав коммита
260

Nikitos's avatar
Nikitos включено в состав коммита
261
262
**macOS в CI не собирается.** Его собирает локальный `build_and_push.sh` и
кладёт в `dist/` репозитория.
Nikitos's avatar
Nikitos включено в состав коммита
263

Nikitos's avatar
Nikitos включено в состав коммита
264
265
Когда пайплайн включат, он будет работать так: два параллельных job'а сборки и
третий, публикующий релиз.
Nikitos's avatar
Nikitos включено в состав коммита
266

Nikitos's avatar
Nikitos включено в состав коммита
267
268
269
270
271
| Job | Что делает |
|---|---|
| `linux` | apt-зависимости, затем `tools/autobuild.py --no-docker --platforms linux-x86_64` |
| `windows` | mingw-w64 + freetype под MinGW, затем `--platforms windows-x86_64` |
| `release` | `needs: [linux, windows]`: забирает оба архива, считает `SHA256SUMS.txt`, создаёт релиз |
272

Nikitos's avatar
Nikitos включено в состав коммита
273
274
275
276
Сборка идёт **без Docker**: облачный раннер GitVerse — это уже изолированный
контейнер, Docker внутри него недоступен. Тот же `tools/autobuild.py`, что и
локально, собирает Linux нативно, а Windows — кросс-компилятором MinGW на этой
же машине.
Nikitos's avatar
Nikitos включено в состав коммита
277

Nikitos's avatar
Nikitos включено в состав коммита
278
Артефакты, которые он бы собрал:
Nikitos's avatar
Nikitos включено в состав коммита
279

Nikitos's avatar
Nikitos включено в состав коммита
280
281
282
283
284
| Файл | Размер (ориентир) |
|---|---|
| `russiano2d-linux-x86_64.tar.gz` | ~10 МБ |
| `russiano2d-windows-x86_64.zip` | ~11 МБ |
| `SHA256SUMS.txt` | байты |
Nikitos's avatar
Nikitos включено в состав коммита
285

Nikitos's avatar
Nikitos включено в состав коммита
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
### 5.2. Windows: MinGW-кросс-компиляция

Windows собирается **mingw-w64 на Linux-раннере**, а не MSVC. Так же его собирает
локальный `tools/autobuild.py --with-windows`, и именно так собран
`dist/windows-x86_64/russiano2d.exe`, лежащий в репозитории. Один способ сборки
на локальную машину и на CI — значит, одна и та же ловушка не всплывает дважды.

Что нужно, кроме самого `mingw-w64`:

* **freetype под MinGW** — его требует RmlUi, а готового пакета в Ubuntu нет.
  Поэтому freetype собирается из исходников (`--host=x86_64-w64-mingw32`,
  `--without-harfbuzz --without-bzip2 --without-brotli --without-png
  --without-zlib`) и ставится в кросс-корень `/usr/x86_64-w64-mingw32` — ровно
  это делает `tools/docker/Dockerfile.linux-builder` при `WITH_MINGW=1`;
* рантайм MinGW линкуется статически (см. `CMakeLists.txt`), иначе `.exe`
  требовал бы рядом `libstdc++-6.dll` и `libgcc_s_seh-1.dll`.

Кросс-корень переопределяется переменной окружения `R2D_MINGW_ROOT`:
`tools/autobuild.py` подставляет её в `CMAKE_FIND_ROOT_PATH`.

### 5.3. Секрет для публикации релиза

Релиз создаёт действие `actions/create-release@v1`, ему нужен публичный
API-ключ GitVerse:

1. GitVerse → настройки профиля → API-ключи → создать ключ с правом
   **«Репозитории → Запись»**;
2. Настройки репозитория → «Секреты и переменные» → секрет **`RELEASE_API_KEY`**.
Nikitos's avatar
Nikitos включено в состав коммита
314

Nikitos's avatar
Nikitos включено в состав коммита
315
316
317
> **Имя секрета не должно начинаться с `GITVERSE_`** — платформа запрещает
> такие префиксы. Пример с `secrets.GITVERSE_API_KEY` в документации GitVerse
> неверен: секрет с таким именем создать не даст.
Nikitos's avatar
Nikitos включено в состав коммита
318

Nikitos's avatar
Nikitos включено в состав коммита
319
Без секрета сборка пройдёт, а `release`-job упадёт на авторизации.
Nikitos's avatar
Nikitos включено в состав коммита
320

Nikitos's avatar
Nikitos включено в состав коммита
321
### 5.4. Лимиты и кэш
Nikitos's avatar
Nikitos включено в состав коммита
322

Nikitos's avatar
Nikitos включено в состав коммита
323
324
Облачные раннеры GitVerse дают **1000 минут** в месяц публичным репозиториям,
но **не больше 30 минут на одну задачу**. Отсюда два решения:
Nikitos's avatar
Nikitos включено в состав коммита
325

Nikitos's avatar
Nikitos включено в состав коммита
326
327
328
329
330
331
332
333
334
335
336
337
338
* Linux и Windows — **параллельные** job'ы: у каждого свой бюджет в 30 минут,
  и они не отнимают время друг у друга;
* зависимости кэшируются (`actions/cache`): CMake FetchContent тянет с github.com
  SDL3, SDL3_image, SDL3_mixer, QuickJS-ng, Box2D, RmlUi, Dear ImGui, glslang и
  SPIRV-Cross — это самая долгая часть сборки.

Ключ кэша статический (`r2d-deps-linux-v1`, `r2d-deps-windows-v1`): пины
зависимостей заданы коммитами в `cmake/Dependencies.cmake` и
`cmake/Shaders.cmake`, поэтому ключ не «залипает». Меняете пины — поднимаете
версию в ключе.

Артефакты между job'ами тоже расходуют квоту: **500 МБ на все репозитории**,
срок жизни — 30 дней. Один прогон кладёт ~21 МБ.
Nikitos's avatar
Nikitos включено в состав коммита
339
340
341

### 5.5. Чего в CI не бывает и почему

Nikitos's avatar
Nikitos включено в состав коммита
342
343
344
345
346
* **Тесты не запускаются.** Пайплайн только собирает и публикует: прогон
  `tools/run_tests.py` и агентские тесты требуют реального SDL_GPU-устройства
  (Vulkan / Metal / DirectX 12), которого на раннере нет. Это локальный шаг.
  Отдельно отметим: старый GitLab-пайплайн тесты декларировал, но ни разу не
  выполнялся, так что регрессии ловил не он, а локальный прогон.
Nikitos's avatar
Nikitos включено в состав коммита
347
348
349
350
351
352
353
354
355
356
* **Агентские тесты** (`tests/agent/*_test.py`) не запускаются нигде в CI.
  Они поднимают движок с `--headless`, но движок всё равно создаёт
  SDL_GPU-устройство (Vulkan / Metal / DirectX 12). На раннере GPU-драйвера
  нет — прогон либо падает, либо уходит в `skip`. Это локальный шаг:
  `python3 tools/run_tests.py`.
* **Сборка игры в один файл** (`./russiano2d build ...`) не проверяется в CI:
  она шифрует груз и на macOS переподписывает бинарник (`codesign`).
  Локально: `tests/agent/build_test.py`.
* **GPU-скриншоты и рендер-проверки** — та же причина. Проверяются глазами
  и локальными агентскими тестами.
Nikitos's avatar
Nikitos включено в состав коммита
357
* **macOS не собирается** — его собирает локальный `build_and_push.sh`.
Nikitos's avatar
Nikitos включено в состав коммита
358
359

Честная формулировка для релизных заметок: CI подтверждает, что движок
Nikitos's avatar
Nikitos включено в состав коммита
360
361
**собирается** под Linux и Windows; всё, что касается тестов, GPU и окна,
проверено локально на машине разработчика.
Nikitos's avatar
Nikitos включено в состав коммита
362

Nikitos's avatar
Nikitos включено в состав коммита
363
### 5.6. Как выпускается релиз сейчас
Nikitos's avatar
Nikitos включено в состав коммита
364

Nikitos's avatar
Nikitos включено в состав коммита
365
CI выключен, поэтому весь выпуск делает локальный `build_and_push.sh`:
Nikitos's avatar
Nikitos включено в состав коммита
366

Nikitos's avatar
Nikitos включено в состав коммита
367
368
369
370
371
372
373
374
375
376
377
1. поднимает патч-версию в `CMakeLists.txt` (`0.1.0 → 0.1.1`);
2. собирает macOS, Linux и Windows в `dist/`;
3. пересобирает `README.md`/`AGENTS.md` в пакетах из `docs/`;
4. коммитит (`git add -A` + `git add -f dist`) и пушит ветку на оба хостинга;
5. ставит аннотированный тег `vX.Y.Z` и пушит его туда же.

Если сборка упала, версия откатывается, а коммит и пуш не делаются
(`ALLOW_PARTIAL=0` по умолчанию).

Релиз — это и есть содержимое `dist/` в коммите, на который указывает тег.
Ссылки для скачивания — в разделе 6.
Nikitos's avatar
Nikitos включено в состав коммита
378

Nikitos's avatar
Nikitos включено в состав коммита
379
380
Раздел 5.1–5.5 описывает пайплайн на GitVerse: он написан и лежит в
репозитории, но выключен — пригодится, если локальная сборка надоест.
Nikitos's avatar
Nikitos включено в состав коммита
381
382
383
384
385

---

## 6. Публикация

Nikitos's avatar
Nikitos включено в состав коммита
386
387
388
389
390
391
392
393
394
395
396
397
**Релиз — это `dist/` в репозитории.** Отдельной выгрузки релизов ни на одном
хостинге нет: архивы, распакованные каталоги и `SHA256SUMS.txt` лежат прямо в
`dist/` и раздаются оттуда.

* <https://hub.mos.ru/dem4ev48/russiano2d/-/tree/main/dist>
* <https://gitverse.ru/Nikide/russiano2d/content/main/dist>

Тег `vX.Y.Z` — метка релиза: он указывает, какому коммиту соответствует
содержимое `dist/`. Ставит и пушит его `build_and_push.sh` (шаг 5).

Мульти-пуш: у `origin` две push-цели — hub.mos.ru (первая) и gitverse.ru
(вторая), поэтому один `git push` уходит сразу на оба хостинга.
Nikitos's avatar
Nikitos включено в состав коммита
398
399
400

```bash
git remote -v
Nikitos's avatar
Nikitos включено в состав коммита
401
402
403
404
# origin    git@hub.mos.ru:dem4ev48/russiano2d.git  (fetch)
# origin    git@hub.mos.ru:dem4ev48/russiano2d.git  (push)
# origin    git@gitverse.ru:Nikide/russiano2d.git   (push)
# gitverse  git@gitverse.ru:Nikide/russiano2d.git
Nikitos's avatar
Nikitos включено в состав коммита
405
406
407
408
409
```

Отправка ветки и тега:

```bash
Nikitos's avatar
Nikitos включено в состав коммита
410
git push origin main          # основная ветка — сразу на оба хоста
Nikitos's avatar
Nikitos включено в состав коммита
411
412
413
git push origin --all         # все ветки
git push origin --tags        # теги
git push origin v0.2.0        # конкретный тег
Nikitos's avatar
Nikitos включено в состав коммита
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
git push gitverse main        # только на GitVerse
```

Проверить, что доехало до каждого хоста:

```bash
git ls-remote origin main        # hub.mos.ru (ls-remote берёт fetch-адрес)
git ls-remote gitverse main      # GitVerse
git ls-remote origin v0.2.0      # тег
```

Обе команды должны показать один и тот же SHA — тот, что у вас в
`git rev-parse main`.

**Порядок целей важен.** `git` идёт по push-адресам по очереди, и что станет
с остальными — зависит от типа ошибки:

| Что случилось с хостом | Что делает git | Код возврата |
|---|---|---|
| отклонил ссылку (`non-fast-forward`) | идёт к следующему адресу | `1` |
| недоступен (нет репозитория, нет сети, отказ в доступе) | **прекращает обход**, остальные адреса push не получат | `128` |

hub.mos.ru стоит первым специально: если GitVerse отклонит пуш или оборвёт
соединение, пуш в Москву уже состоялся. Обратная сторона — при недоступном
hub.mos.ru зеркало тоже не обновится; тогда досылайте отдельно:
`git push gitverse main`.

В обоих случаях код возврата ненулевой, даже если часть хостов коммит уже
получила, — поэтому `build_and_push.sh` после пуша отдельно проверяет каждый
адрес и печатает, кто отстал.

Если GitVerse отверг пуш (например, разошлась история — см. раздел 7), а в
Москву всё уехало, повторяйте только проблемный хост:

```bash
git push gitverse main
Nikitos's avatar
Nikitos включено в состав коммита
450
451
452
```

`--mirror` для публикации не используйте: он удаляет на сервере всё, чего нет
Nikitos's avatar
Nikitos включено в состав коммита
453
454
локально. Локальный `git clone --mirror` в отдельный каталог остаётся
дополнительным бэкапом.
Nikitos's avatar
Nikitos включено в состав коммита
455
456
457
458
459
460
461
462
463
464
465
466
467
468

---

## 7. Расхождение историй

Симптом: `git push origin main` отклонён (`non-fast-forward`), потому что на
зеркале есть коммиты, которых нет локально (например, при инициализации
репозитория через веб-интерфейс GitLab).

Что делать — по порядку:

1. Посмотреть, что именно разошлось:

   ```bash
Nikitos's avatar
Nikitos включено в состав коммита
469
470
   git fetch gitverse
   git log --oneline --left-right --graph main...gitverse/main
Nikitos's avatar
Nikitos включено в состав коммита
471
472
473
474
475
476
   ```

2. Если на зеркале осмысленная история (README, `.gitignore`) — слить, не
   перезаписывая:

   ```bash
Nikitos's avatar
Nikitos включено в состав коммита
477
   git merge --allow-unrelated-histories gitverse/main
Nikitos's avatar
Nikitos включено в состав коммита
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
   # разрулить конфликты, закоммитить
   git push origin main
   ```

3. Если история на зеркале — мусор от неудачной инициализации и владелец
   согласен её заменить:

   ```bash
   git push --force-with-lease origin main
   ```

   `--force-with-lease` безопаснее `--force`: push пройдёт, только если
   зеркало не изменилось с последнего `fetch`.

4. Никогда не делать `git push --force` «на всякий случай» в `origin`:
   перезапись публичной истории ломает форки и PR. Принудительный push —
   только после явного решения владельца.

Тег, указывающий на уже удалённый коммит, после перезаписи истории лучше
пересоздать осознанно:

```bash
Nikitos's avatar
Nikitos включено в состав коммита
500
git push --delete gitverse v0.2.0   # если тег успел уехать
Nikitos's avatar
Nikitos включено в состав коммита
501
502
503
504
505
506
507
508
509
510
511
git tag -d v0.2.0
python3 tools/release.py --version 0.2.0 --skip-build --skip-tests
```

---

## 8. Чек-лист релиза

- [x] Лицензия выбрана, `LICENSE` лежит в корне, упомянута в `README.md`
- [ ] Права на демо-арт и звук подтверждены (или файлы исключены)
- [x] Путь до репозитория уточнён: <https://hub.mos.ru/dem4ev48/russiano2d>
Nikitos's avatar
Nikitos включено в состав коммита
512
      (зеркало — <https://gitverse.ru/Nikide/russiano2d>)
Nikitos's avatar
Nikitos включено в состав коммита
513
514
515
- [ ] `CHANGELOG.md` обновлён, `[Unreleased]` разобран
- [ ] Локально: `python3 tools/run_tests.py` — без `fail`; `skip` объяснены
- [ ] Локально: собраны Release-бинарники (при необходимости — игры в один файл)
Nikitos's avatar
Nikitos включено в состав коммита
516
517
- [ ] `./build_and_push.sh` — поднял версию, собрал `dist/`, запушил ветку и тег
      (или вручную: `python3 tools/release.py --version x.y.z`, затем раздел 6)
Nikitos's avatar
Nikitos включено в состав коммита
518
519
- [ ] `dist/*/SHA256SUMS.txt` проверен (`shasum -a 256 -c`)
- [ ] `THIRD_PARTY_NOTICES.md` актуален; тексты лицензий в поставке
Nikitos's avatar
Nikitos включено в состав коммита
520
521
522
523
524
- [ ] Ветка и тег `vX.Y.Z` доехали до обоих хостов (скрипт печатает сверку;
      вручную — `git ls-remote origin main` и `git ls-remote origin vX.Y.Z`)
- [ ] `dist/` в коммите тега открывается по ссылке и архивы скачиваются:
      <https://hub.mos.ru/dem4ev48/russiano2d/-/tree/main/dist> и
      <https://gitverse.ru/Nikide/russiano2d/content/main/dist>
Nikitos's avatar
Nikitos включено в состав коммита
525
- [ ] Локальный mirror-бэкап обновлён (`git clone --mirror` или бэкап инстанса)