fix: сравнение с origin через merge-base (расхождение веток)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shop
2026-05-17 14:46:36 +03:00
parent c5e8653b30
commit db6ab9a701
2 changed files with 64 additions and 7 deletions
+53 -6
View File
@@ -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) {
+11 -1
View File
@@ -50,8 +50,18 @@
<% if (git.behind != null) { %>
<dt>На origin/main</dt>
<dd>
<% if (git.behind > 0) { %>
<% if (git.remoteShort) { %>
<span class="muted">Удалённо: <code><%= git.remoteShort %></code></span><br>
<% } %>
<% if (git.diverged) { %>
<span class="badge badge--warn">Истории разошлись</span>
<span class="badge badge--sale">Можно подтянуть: <%= git.behind %> комм.</span>
<span class="muted">Локально впереди на <%= git.ahead %> комм.</span>
<p class="muted" style="margin:0.35rem 0 0;font-size:0.85rem">Обновление из админки сделает <code>git pull</code> (как на origin). Локальные коммиты могут быть сброшены.</p>
<% } else if (git.behind > 0) { %>
<span class="badge badge--sale">Доступно обновлений: <%= git.behind %></span>
<% } else if (git.ahead > 0) { %>
<span class="badge badge--warn">Локально впереди origin на <%= git.ahead %> комм.</span>
<% } else { %>
<span class="badge">Актуально</span>
<% } %>