Upload via Git Manager GUI - main.js

This commit is contained in:
2026-03-25 22:40:37 +00:00
parent aafd5c3e66
commit 925c214a1f

136
main.js
View File

@@ -41,6 +41,7 @@ const {
// GitHub
listGithubRepos,
getGithubCurrentUser,
githubRepoExists,
getGithubUserHeatmap,
getGithubRepoContents,
getGithubFileContent,
@@ -56,6 +57,7 @@ const {
editGithubRelease,
deleteGithubRelease,
updateGithubRepoVisibility,
updateGithubRepoDefaultBranch,
updateGithubRepoTopics,
deleteGithubRepo
} = require('./src/git/apiHandler.js');
@@ -3507,6 +3509,7 @@ ipcMain.handle('check-clone-target-collisions', async (_event, data) => {
ipcMain.handle('sync-repo-to-github', async (event, data) => {
let mirrorDir = null;
let sourceDefaultBranch = '';
try {
const creds = readCredentials();
if (!creds?.githubToken) {
@@ -3542,8 +3545,16 @@ ipcMain.handle('sync-repo-to-github', async (event, data) => {
repoCreated = true;
} catch (e) {
const msg = String(e?.message || e || '');
if (!/already exists|existiert bereits|name already exists/i.test(msg)) {
throw e;
if (!/already exists|already_exists|existiert bereits|name already exists/i.test(msg)) {
// Falls GitHub mit einer unscharfen Meldung antwortet, explizit auf Existenz pruefen.
const exists = await githubRepoExists({
token: creds.githubToken,
owner: targetOwner,
repo
}).catch(() => false);
if (!exists) {
throw e;
}
}
}
@@ -3575,29 +3586,133 @@ ipcMain.handle('sync-repo-to-github', async (event, data) => {
} catch (_) {}
mirrorDir = fs.mkdtempSync(ppath.join(os.tmpdir(), 'git-manager-sync-'));
const cloneArgs = cloneNeedsGiteaHeader
? ['-c', `http.${sourceOrigin}.extraheader=AUTHORIZATION: token ${creds.giteaToken}`, 'clone', '--mirror', sourceCloneUrl, mirrorDir]
: ['clone', '--mirror', sourceCloneUrl, mirrorDir];
// --bare statt --mirror: lädt alle Branches/Tags ohne mirror-Flag zu setzen,
// damit anschließende Refspec-Pushes (Force) funktionieren.
const cloneArgs = cloneNeedsGiteaHeader
? ['-c', `http.${sourceOrigin}.extraheader=AUTHORIZATION: token ${creds.giteaToken}`, 'clone', '--bare', sourceCloneUrl, mirrorDir]
: ['clone', '--bare', sourceCloneUrl, mirrorDir];
// runGit: gibt stdout zurück, loggt stderr immer (git push schreibt Status auf stderr)
const runGit = (args, cwd) => {
const res = spawnSync('git', args, {
cwd,
encoding: 'utf8',
windowsHide: true
});
const out = (res.stdout || '').trim();
const err = (res.stderr || '').trim();
if (err) console.log(`[sync-repo-to-github][git] ${err}`);
if (res.status !== 0) {
throw new Error((res.stderr || res.stdout || 'Git-Fehler').trim());
throw new Error((err || out || 'Git-Fehler').trim());
}
return (res.stdout || '').trim();
return out;
};
console.log(`[sync-repo-to-github] Klone Quell-Repo (bare): ${sourceCloneUrl}`);
runGit(cloneArgs, process.cwd());
console.log('[sync-repo-to-github] Bare-Clone abgeschlossen');
// Alle lokalen Branches auflisten
const allBranchesRaw = runGit(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], mirrorDir);
const allBranches = (allBranchesRaw || '').split('\n').map(s => s.trim()).filter(s => s && isSafeGitRef(s));
console.log(`[sync-repo-to-github] Lokale Branches: ${allBranches.join(', ') || '(keine)'}`);
// Default-Branch ermitteln
try {
const headRef = runGit(['symbolic-ref', '--short', 'HEAD'], mirrorDir);
sourceDefaultBranch = String(headRef || '').replace(/^refs\/heads\//, '').trim();
if (!isSafeGitRef(sourceDefaultBranch)) sourceDefaultBranch = '';
} catch (_) {
sourceDefaultBranch = allBranches[0] || '';
}
if (!sourceDefaultBranch && allBranches.length > 0) sourceDefaultBranch = allBranches[0];
console.log(`[sync-repo-to-github] Quell-Default-Branch: "${sourceDefaultBranch || '(unbekannt)'}"`);
const githubRemoteUrl = `https://github.com/${targetOwner}/${repo}.git`;
const githubAuthHeader = `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${creds.githubToken}`, 'utf8').toString('base64')}`;
const ghAuthArgs = ['-c', 'credential.helper=', '-c', `http.https://github.com/.extraheader=${githubAuthHeader}`];
runGit(['remote', 'set-url', '--push', 'origin', githubRemoteUrl], mirrorDir);
runGit(['-c', 'credential.helper=', '-c', `http.https://github.com/.extraheader=${githubAuthHeader}`, 'push', '--mirror'], mirrorDir);
// GitHub als Push-Remote setzen
runGit(['remote', 'set-url', 'origin', githubRemoteUrl], mirrorDir);
// Lokale Commit-SHAs loggen (= Gitea-Stand)
for (const branch of allBranches) {
try {
const localSha = runGit(['rev-parse', `refs/heads/${branch}`], mirrorDir);
console.log(`[sync-repo-to-github] Gitea ${branch}: ${localSha}`);
} catch (_) {}
}
// Remote (GitHub) Commit-SHAs VOR dem Push ermitteln und vergleichen
try {
const remoteRefsRaw = runGit([...ghAuthArgs, 'ls-remote', '--heads', 'origin'], mirrorDir);
const remoteMap = {};
(remoteRefsRaw || '').split('\n').forEach(line => {
const parts = line.trim().split(/\s+/);
if (parts.length === 2) {
const branch = parts[1].replace('refs/heads/', '');
remoteMap[branch] = parts[0];
}
});
for (const branch of allBranches) {
const remoteSha = remoteMap[branch] || '(nicht vorhanden)';
console.log(`[sync-repo-to-github] GitHub ${branch}: ${remoteSha}`);
}
} catch (lsErr) {
console.warn('[sync-repo-to-github] ls-remote Warnung:', lsErr?.message || lsErr);
}
// Jeden Branch einzeln force-pushen für maximale Zuverlässigkeit
console.log(`[sync-repo-to-github] Force-Push ${allBranches.length} Branch(es) nach GitHub: ${githubRemoteUrl}`);
let pushedCount = 0;
for (const branch of allBranches) {
console.log(`[sync-repo-to-github] Pushe Branch: ${branch}`);
runGit([...ghAuthArgs, 'push', '--force', 'origin', `refs/heads/${branch}:refs/heads/${branch}`], mirrorDir);
pushedCount++;
}
console.log(`[sync-repo-to-github] ${pushedCount} Branch(es) erfolgreich gepusht`);
// Auf GitHub nicht mehr vorhandene Branches aufräumen (prune via API, non-fatal)
try {
const remoteRefsRaw = runGit([...ghAuthArgs, 'ls-remote', '--heads', 'origin'], mirrorDir);
const remoteHeads = (remoteRefsRaw || '').split('\n')
.map(l => { const m = l.match(/refs\/heads\/(.+)$/); return m ? m[1].trim() : null; })
.filter(Boolean);
const localSet = new Set(allBranches);
for (const remoteBranch of remoteHeads) {
if (!localSet.has(remoteBranch) && isSafeGitRef(remoteBranch)) {
console.log(`[sync-repo-to-github] Lösche veralteten Remote-Branch: ${remoteBranch}`);
runGit([...ghAuthArgs, 'push', 'origin', `--delete`, remoteBranch], mirrorDir);
}
}
} catch (pruneErr) {
console.warn('[sync-repo-to-github] Prune-Warnung:', pruneErr?.message || pruneErr);
}
// Tags mit Force-Push übertragen (non-fatal)
try {
runGit([...ghAuthArgs, 'push', 'origin', '--tags', '--force'], mirrorDir);
console.log('[sync-repo-to-github] Tags erfolgreich gepusht');
} catch (tagsErr) {
console.warn('[sync-repo-to-github] Tags-Push Warnung:', tagsErr?.message || tagsErr);
}
// Default-Branch auf GitHub per API angleichen
if (sourceDefaultBranch) {
console.log(`[sync-repo-to-github] Setze GitHub Default-Branch auf: ${sourceDefaultBranch}`);
try {
await updateGithubRepoDefaultBranch({
token: creds.githubToken,
owner: targetOwner,
repo,
defaultBranch: sourceDefaultBranch
});
console.log('[sync-repo-to-github] Default-Branch aktualisiert');
} catch (defaultBranchErr) {
console.warn('sync-repo-to-github: default-branch update warning', defaultBranchErr?.message || defaultBranchErr);
}
}
// Optional: Topics auf GitHub angleichen (nur falls übergeben)
const topics = Array.isArray(data?.topics) ? data.topics.filter(Boolean) : [];
@@ -3617,7 +3732,8 @@ ipcMain.handle('sync-repo-to-github', async (event, data) => {
return {
ok: true,
repoCreated,
githubRepo: `${targetOwner}/${repo}`
githubRepo: `${targetOwner}/${repo}`,
sourceDefaultBranch: sourceDefaultBranch || null
};
} catch (e) {
console.error('sync-repo-to-github error', e);