Upload via Git Manager GUI - main.js
This commit is contained in:
136
main.js
136
main.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user