Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af4c2616f2 | |||
| 3877a6f2a6 |
@@ -135,6 +135,7 @@ jobs:
|
|||||||
ARTIFACT_MAC: ${{ github.workspace }}/_mac
|
ARTIFACT_MAC: ${{ github.workspace }}/_mac
|
||||||
ARTIFACT_LINUX: ${{ github.workspace }}/release
|
ARTIFACT_LINUX: ${{ github.workspace }}/release
|
||||||
DND_FEED_TMP_ROOT: ${{ github.workspace }}/.dnd-feed-tmp
|
DND_FEED_TMP_ROOT: ${{ github.workspace }}/.dnd-feed-tmp
|
||||||
|
DND_UPDATES_SQUASH_HISTORY: '1'
|
||||||
GIT_COMMIT_TAG: ${{ github.ref_name }}
|
GIT_COMMIT_TAG: ${{ github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -378,9 +378,21 @@ git push origin v1.0.1
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Линейная история в `UPDATES_REPO` (например DndGamePlayerUpdates)
|
||||||
|
|
||||||
|
Переписывается **только** публичный репозиторий из секрета **`UPDATES_REPO`**, ветка **`updates`**. Репозиторий с исходниками игры (**DndGamePlayer**) и его теги **не меняются**.
|
||||||
|
|
||||||
|
В workflow задано **`DND_UPDATES_SQUASH_HISTORY=1`**: после merge и подкладки артефактов скрипт делает **`git checkout --orphan`** → один корневой коммит → push с **`--force-with-lease`**, если локально уже есть **`refs/remotes/origin/updates`** (ветка на сервере была); иначе — обычный первый **`git push -u origin updates`**. Так на каждом релизе в истории остаётся **один** коммит с актуальным набором файлов — меньше трафик и диск при `clone`/`fetch` на раннере.
|
||||||
|
|
||||||
|
На **сервере Gitea** старые объекты коммитов остаются «висячими», пока не отработает **сборка мусора** репозитория (настройки сервера / ручной `git gc` в bare-репо). Для пользователей приложения важны только URL **`latest*.yml`** и установщиков — они не меняются по смыслу.
|
||||||
|
|
||||||
|
Не запускайте **два релиза**, которые одновременно пушат feed, — возможна гонка и отказ **`--force-with-lease`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Если push отклонён: `(fetch first)` / `rejected`
|
## Если push отклонён: `(fetch first)` / `rejected`
|
||||||
|
|
||||||
Пока job собирает артефакты, в **`updates`** мог успеть попасть **другой** коммит (второй релиз, ручная выкладка). Скрипт `sync-update-feed.mjs` перед push делает **`git fetch` + `git merge origin/updates`** (и после клона — то же в начале), плюс shallow **глубина по умолчанию 40** (`DND_UPDATES_CLONE_DEPTH`). Не запускайте **два релиза одного и того же репо одновременно** по двум тегам — возможны конфликты merge.
|
Пока job собирает артефакты, в **`updates`** мог успеть попасть **другой** коммит (второй релиз, ручная выкладка). Скрипт `sync-update-feed.mjs` перед push (в режиме **без** squash) делает **`git fetch` + `git merge origin/updates`** (и после клона — то же в начале), плюс shallow **глубина** (`DND_UPDATES_CLONE_DEPTH`: по умолчанию **40**, при **`DND_UPDATES_SQUASH_HISTORY=1`** в скрипте по умолчанию **8**). В режиме **squash** перед push merge **не** выполняется — уже смерженное дерево сжимается в один коммит и отправляется force-with-lease. Не запускайте **два релиза одного и того же репо одновременно** по двум тегам — возможны конфликты merge.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "DndGamePlayer",
|
"name": "DndGamePlayer",
|
||||||
"version": "1.0.10",
|
"version": "1.0.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "DndGamePlayer",
|
"name": "DndGamePlayer",
|
||||||
"version": "1.0.10",
|
"version": "1.0.11",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "DndGamePlayer",
|
"name": "DndGamePlayer",
|
||||||
"version": "1.0.10",
|
"version": "1.0.11",
|
||||||
"description": "DNDGamePlayer — редактор и проигрыватель игр",
|
"description": "DNDGamePlayer — редактор и проигрыватель игр",
|
||||||
"main": "dist/main/index.cjs",
|
"main": "dist/main/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
* DND_FEED_TMP_ROOT — каталог для временного клона feed (по умолчанию GITHUB_WORKSPACE / TMPDIR / os.tmpdir); не используйте узкий /tmp на раннере
|
* DND_FEED_TMP_ROOT — каталог для временного клона feed (по умолчанию GITHUB_WORKSPACE / TMPDIR / os.tmpdir); не используйте узкий /tmp на раннере
|
||||||
* DND_GIT_FETCH_RETRIES — число попыток git fetch (1–6, по умолчанию 6); между попытками — git gc --prune=now (освобождение после обрыва TLS/unpack)
|
* DND_GIT_FETCH_RETRIES — число попыток git fetch (1–6, по умолчанию 6); между попытками — git gc --prune=now (освобождение после обрыва TLS/unpack)
|
||||||
* DND_FEED_SKIP_DISK_CHECK — если "1", не проверять свободное место на томе DND_FEED_TMP_ROOT перед clone
|
* DND_FEED_SKIP_DISK_CHECK — если "1", не проверять свободное место на томе DND_FEED_TMP_ROOT перед clone
|
||||||
|
* DND_UPDATES_SQUASH_HISTORY — если "1", после каждого релиза ветка updates в UPDATES_REPO — один корневой коммит (orphan + force-with-lease push), история не копится (меньше места на раннере и на сервере после GC)
|
||||||
*/
|
*/
|
||||||
import { execFileSync, spawnSync } from 'node:child_process';
|
import { execFileSync, spawnSync } from 'node:child_process';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
@@ -175,6 +176,55 @@ function sleepSyncSeconds(seconds) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function feedSquashSingleCommitEnabled() {
|
||||||
|
return process.env.DND_UPDATES_SQUASH_HISTORY?.trim() === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** После merge и add: одна линия без истории (только репо UPDATES_REPO / ветка updates). */
|
||||||
|
function squashUpdatesBranchToSingleCommit(work, message) {
|
||||||
|
const orphan = 'dnd-feed-squash-tmp';
|
||||||
|
runGit(['checkout', '--orphan', orphan], work);
|
||||||
|
runGit(['add', '-A'], work);
|
||||||
|
runGit(['commit', '-m', message], work);
|
||||||
|
runGit(['branch', '-D', 'updates'], work);
|
||||||
|
runGit(['branch', '-m', 'updates'], work);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hadOriginUpdatesRemoteRef(work) {
|
||||||
|
try {
|
||||||
|
execFileSync('git', ['show-ref', '--verify', '--quiet', 'refs/remotes/origin/updates'], {
|
||||||
|
cwd: work,
|
||||||
|
stdio: 'ignore',
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** После squash не делаем merge перед push — вернёт старую историю с remote. */
|
||||||
|
function pushUpdatesBranchAfterSquash(work) {
|
||||||
|
const retries = Math.max(1, Math.min(5, Number.parseInt(process.env.DND_GIT_PUSH_RETRIES || '3', 10) || 3));
|
||||||
|
const args = hadOriginUpdatesRemoteRef(work)
|
||||||
|
? ['push', '--force-with-lease', '-u', 'origin', 'updates']
|
||||||
|
: ['push', '-u', 'origin', 'updates'];
|
||||||
|
let lastError;
|
||||||
|
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
||||||
|
try {
|
||||||
|
console.log(`[sync-update-feed] push updates (squash, attempt ${attempt}/${retries})`);
|
||||||
|
execFileSync('git', args, { cwd: work, stdio: 'inherit' });
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
lastError = err;
|
||||||
|
console.warn(`[sync-update-feed] push failed (attempt ${attempt}/${retries})`);
|
||||||
|
if (attempt < retries) {
|
||||||
|
sleepSyncSeconds(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
function mergeOriginUpdates(work) {
|
function mergeOriginUpdates(work) {
|
||||||
const fetchRetries = Math.max(
|
const fetchRetries = Math.max(
|
||||||
1,
|
1,
|
||||||
@@ -252,10 +302,10 @@ function main() {
|
|||||||
const tmp = fs.mkdtempSync(path.join(tmpRoot, 'dnd-feed-'));
|
const tmp = fs.mkdtempSync(path.join(tmpRoot, 'dnd-feed-'));
|
||||||
const work = path.join(tmp, 'repo');
|
const work = path.join(tmp, 'repo');
|
||||||
|
|
||||||
const cloneDepth = Math.max(
|
const squash = feedSquashSingleCommitEnabled();
|
||||||
2,
|
const cloneDepth = squash
|
||||||
Math.min(200, Number.parseInt(process.env.DND_UPDATES_CLONE_DEPTH || '40', 10) || 40),
|
? Math.max(1, Math.min(200, Number.parseInt(process.env.DND_UPDATES_CLONE_DEPTH || '8', 10) || 8))
|
||||||
);
|
: Math.max(2, Math.min(200, Number.parseInt(process.env.DND_UPDATES_CLONE_DEPTH || '40', 10) || 40));
|
||||||
|
|
||||||
const gh = gitHttpInlineFlags();
|
const gh = gitHttpInlineFlags();
|
||||||
try {
|
try {
|
||||||
@@ -290,8 +340,17 @@ function main() {
|
|||||||
runGit(['add', '-A'], work);
|
runGit(['add', '-A'], work);
|
||||||
const st = execFileSync('git', ['status', '--porcelain'], { cwd: work }).toString().trim();
|
const st = execFileSync('git', ['status', '--porcelain'], { cwd: work }).toString().trim();
|
||||||
if (st) {
|
if (st) {
|
||||||
runGit(['commit', '-m', `update feed ${tag}`], work);
|
const msg = `update feed ${tag}`;
|
||||||
|
if (squash) {
|
||||||
|
console.warn(
|
||||||
|
'[sync-update-feed] DND_UPDATES_SQUASH_HISTORY=1: одна линия в UPDATES_REPO (orphan + force-with-lease при наличии origin/updates)',
|
||||||
|
);
|
||||||
|
squashUpdatesBranchToSingleCommit(work, msg);
|
||||||
|
pushUpdatesBranchAfterSquash(work);
|
||||||
|
} else {
|
||||||
|
runGit(['commit', '-m', msg], work);
|
||||||
pushUpdatesBranch(work);
|
pushUpdatesBranch(work);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('[sync-update-feed] nothing to commit (identical artifacts?)');
|
console.warn('[sync-update-feed] nothing to commit (identical artifacts?)');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user