diff --git a/src/services/git-deploy.js b/src/services/git-deploy.js index f01e883..5f1bb74 100644 --- a/src/services/git-deploy.js +++ b/src/services/git-deploy.js @@ -117,17 +117,57 @@ async function gitCmd(args, cwd, { needsWrite = false } = {}) { } } -/** Сколько коммитов на origin/main без записи в .git (без fetch) */ -async function getCommitsBehind(root) { +/** + * Сравнение с origin/main через ls-remote + merge-base (без fetch, без записи в .git). + */ +async function getRemoteSyncStatus(root) { const localHead = (await gitCmd(['rev-parse', 'HEAD'], root)).split('\n')[0].trim(); const remoteOut = await gitCmd(['ls-remote', 'origin', 'refs/heads/main'], root); const remoteSha = remoteOut.split(/\s+/)[0]?.trim(); if (!remoteSha) { throw new Error('Не найден refs/heads/main на origin'); } - if (remoteSha === localHead) return 0; - const count = await gitCmd(['rev-list', '--count', `${localHead}..${remoteSha}`], root); - return parseInt(count, 10) || 0; + + const remoteShort = ( + await gitCmd(['rev-parse', '--short', remoteSha], root) + ).split('\n')[0].trim(); + + if (remoteSha === localHead) { + return { behind: 0, ahead: 0, diverged: false, remoteShort, remoteSha }; + } + + let mergeBase; + try { + mergeBase = (await gitCmd(['merge-base', localHead, remoteSha], root)).split('\n')[0].trim(); + } catch { + throw new Error( + 'Не удалось сравнить с origin/main (нет общего предка). Выполните на сервере: git fetch && git reset --hard origin/main' + ); + } + + if (!mergeBase) { + throw new Error('Нет общего предка с origin/main'); + } + + let behind = 0; + let ahead = 0; + + if (mergeBase !== remoteSha) { + const behindStr = await gitCmd(['rev-list', '--count', `${mergeBase}..${remoteSha}`], root); + behind = parseInt(behindStr, 10) || 0; + } + if (mergeBase !== localHead) { + const aheadStr = await gitCmd(['rev-list', '--count', `${mergeBase}..${localHead}`], root); + ahead = parseInt(aheadStr, 10) || 0; + } + + return { + behind, + ahead, + diverged: behind > 0 && ahead > 0, + remoteShort, + remoteSha, + }; } async function getGitInfo({ fetchRemote = false } = {}) { @@ -157,6 +197,9 @@ async function getGitInfo({ fetchRemote = false } = {}) { dirty: false, dirtyHint: null, behind: null, + ahead: null, + diverged: false, + remoteShort: null, updateEnabled: isUpdateEnabled(), platform: process.platform, }; @@ -191,7 +234,11 @@ async function getGitInfo({ fetchRemote = false } = {}) { if (fetchRemote) { try { - info.behind = await getCommitsBehind(root); + const sync = await getRemoteSyncStatus(root); + info.behind = sync.behind; + info.ahead = sync.ahead; + info.diverged = sync.diverged; + info.remoteShort = sync.remoteShort; } catch (err) { info.fetchError = err.message; if (repoOwner) { diff --git a/src/views/admin/system.ejs b/src/views/admin/system.ejs index ac0f99a..83245e9 100644 --- a/src/views/admin/system.ejs +++ b/src/views/admin/system.ejs @@ -50,8 +50,18 @@ <% if (git.behind != null) { %>
<%= git.remoteShort %>Обновление из админки сделает git pull (как на origin). Локальные коммиты могут быть сброшены.