fix: сравнение с origin через merge-base (расхождение веток)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 localHead = (await gitCmd(['rev-parse', 'HEAD'], root)).split('\n')[0].trim();
|
||||||
const remoteOut = await gitCmd(['ls-remote', 'origin', 'refs/heads/main'], root);
|
const remoteOut = await gitCmd(['ls-remote', 'origin', 'refs/heads/main'], root);
|
||||||
const remoteSha = remoteOut.split(/\s+/)[0]?.trim();
|
const remoteSha = remoteOut.split(/\s+/)[0]?.trim();
|
||||||
if (!remoteSha) {
|
if (!remoteSha) {
|
||||||
throw new Error('Не найден refs/heads/main на origin');
|
throw new Error('Не найден refs/heads/main на origin');
|
||||||
}
|
}
|
||||||
if (remoteSha === localHead) return 0;
|
|
||||||
const count = await gitCmd(['rev-list', '--count', `${localHead}..${remoteSha}`], root);
|
const remoteShort = (
|
||||||
return parseInt(count, 10) || 0;
|
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 } = {}) {
|
async function getGitInfo({ fetchRemote = false } = {}) {
|
||||||
@@ -157,6 +197,9 @@ async function getGitInfo({ fetchRemote = false } = {}) {
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
dirtyHint: null,
|
dirtyHint: null,
|
||||||
behind: null,
|
behind: null,
|
||||||
|
ahead: null,
|
||||||
|
diverged: false,
|
||||||
|
remoteShort: null,
|
||||||
updateEnabled: isUpdateEnabled(),
|
updateEnabled: isUpdateEnabled(),
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
};
|
};
|
||||||
@@ -191,7 +234,11 @@ async function getGitInfo({ fetchRemote = false } = {}) {
|
|||||||
|
|
||||||
if (fetchRemote) {
|
if (fetchRemote) {
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
info.fetchError = err.message;
|
info.fetchError = err.message;
|
||||||
if (repoOwner) {
|
if (repoOwner) {
|
||||||
|
|||||||
@@ -50,8 +50,18 @@
|
|||||||
<% if (git.behind != null) { %>
|
<% if (git.behind != null) { %>
|
||||||
<dt>На origin/main</dt>
|
<dt>На origin/main</dt>
|
||||||
<dd>
|
<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>
|
<span class="badge badge--sale">Доступно обновлений: <%= git.behind %></span>
|
||||||
|
<% } else if (git.ahead > 0) { %>
|
||||||
|
<span class="badge badge--warn">Локально впереди origin на <%= git.ahead %> комм.</span>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<span class="badge">Актуально</span>
|
<span class="badge">Актуально</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
Reference in New Issue
Block a user