gitxelectron 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/ai-setup.js +1 -1
- package/bin/cli.js +4 -1
- package/out/main/index.js +428 -113
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/index-CbRfLxpa.css +2989 -0
- package/out/renderer/assets/{index-7yJPiur0.js → index-CmvtfcrN.js} +605 -85
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/out/renderer/assets/index-CI-_rJcn.css +0 -2989
package/out/main/index.js
CHANGED
|
@@ -678,6 +678,31 @@ function getGitBinary() {
|
|
|
678
678
|
cachedGitBinary = "git";
|
|
679
679
|
return "git";
|
|
680
680
|
}
|
|
681
|
+
async function ensureGitIdentity(sessionId, git) {
|
|
682
|
+
try {
|
|
683
|
+
const account = getActiveAccount(sessionId);
|
|
684
|
+
if (account && account.username) {
|
|
685
|
+
const email2 = account.id ? `${account.id}+${account.username}@users.noreply.github.com` : `${account.username}@users.noreply.github.com`;
|
|
686
|
+
const curName = await git.getConfig("user.name");
|
|
687
|
+
const curEmail = await git.getConfig("user.email");
|
|
688
|
+
const isPlaceholderName = !curName.value || curName.value === "GitElectron User" || curName.value === "gitelectron" || curName.value === "GitQuickPush User";
|
|
689
|
+
const isPlaceholderEmail = !curEmail.value || curEmail.value === "auto@gitelectron.local" || curEmail.value.includes("gitelectron.local") || curEmail.value.includes("gitquickpush.local");
|
|
690
|
+
if (isPlaceholderName) {
|
|
691
|
+
await git.addConfig("user.name", account.username);
|
|
692
|
+
}
|
|
693
|
+
if (isPlaceholderEmail) {
|
|
694
|
+
await git.addConfig("user.email", email2);
|
|
695
|
+
}
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const name = await git.getConfig("user.name");
|
|
699
|
+
if (!name.value) await git.addConfig("user.name", "GitQuickPush User");
|
|
700
|
+
const email = await git.getConfig("user.email");
|
|
701
|
+
if (!email.value) await git.addConfig("user.email", "noreply@gitquickpush.local");
|
|
702
|
+
} catch (e) {
|
|
703
|
+
console.warn("Failed to ensure git identity:", e);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
681
706
|
function initGit(sessionId, baseDir) {
|
|
682
707
|
recordSessionPath(sessionId, baseDir);
|
|
683
708
|
const options = {
|
|
@@ -710,14 +735,7 @@ function initGit(sessionId, baseDir) {
|
|
|
710
735
|
});
|
|
711
736
|
(async () => {
|
|
712
737
|
try {
|
|
713
|
-
|
|
714
|
-
if (!name.value) {
|
|
715
|
-
await git.addConfig("user.name", "GitElectron User");
|
|
716
|
-
}
|
|
717
|
-
const email = await git.getConfig("user.email");
|
|
718
|
-
if (!email.value) {
|
|
719
|
-
await git.addConfig("user.email", "auto@gitelectron.local");
|
|
720
|
-
}
|
|
738
|
+
await ensureGitIdentity(sessionId, git);
|
|
721
739
|
} catch (e) {
|
|
722
740
|
console.warn("Failed to ensure git identity:", e);
|
|
723
741
|
}
|
|
@@ -1119,19 +1137,7 @@ async function unstageFiles(sessionId, files) {
|
|
|
1119
1137
|
async function commitChanges(sessionId, message) {
|
|
1120
1138
|
const { git } = getGitSession(sessionId);
|
|
1121
1139
|
await ensureAiWeightsUseLfs(git);
|
|
1122
|
-
|
|
1123
|
-
const nameConfig = await git.getConfig("user.name");
|
|
1124
|
-
const emailConfig = await git.getConfig("user.email");
|
|
1125
|
-
if (!nameConfig.value || !emailConfig.value) {
|
|
1126
|
-
const account = getActiveAccount(sessionId);
|
|
1127
|
-
const name = account?.username || "GitQuickPush User";
|
|
1128
|
-
const email = account ? `${account.username}@users.noreply.github.com` : "noreply@gitquickpush.local";
|
|
1129
|
-
if (!nameConfig.value) await git.addConfig("user.name", name);
|
|
1130
|
-
if (!emailConfig.value) await git.addConfig("user.email", email);
|
|
1131
|
-
}
|
|
1132
|
-
} catch (e) {
|
|
1133
|
-
console.warn("Failed to check or set git author identity", e);
|
|
1134
|
-
}
|
|
1140
|
+
await ensureGitIdentity(sessionId, git);
|
|
1135
1141
|
return await git.commit(message);
|
|
1136
1142
|
}
|
|
1137
1143
|
function injectAuthToken(sessionId, remoteUrl) {
|
|
@@ -1333,6 +1339,53 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1333
1339
|
if (onProgress) onProgress("✅ Massive push completed successfully!");
|
|
1334
1340
|
return true;
|
|
1335
1341
|
}
|
|
1342
|
+
function cleanGitErrorMessage(rawMsg, token, branch) {
|
|
1343
|
+
if (!rawMsg) return "Unknown Git error";
|
|
1344
|
+
let msg = typeof rawMsg === "string" ? rawMsg : rawMsg?.message || String(rawMsg);
|
|
1345
|
+
if (token) {
|
|
1346
|
+
msg = msg.split(token).join("***");
|
|
1347
|
+
}
|
|
1348
|
+
msg = msg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
|
|
1349
|
+
msg = msg.replace(/ghp_[A-Za-z0-9_]{30,}/g, "***");
|
|
1350
|
+
msg = msg.replace(/github_pat_[A-Za-z0-9_]{30,}/g, "***");
|
|
1351
|
+
msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
|
|
1352
|
+
msg = msg.replace(/^Error:\s*/i, "");
|
|
1353
|
+
msg = msg.replace(/^fatal:\s*/i, "");
|
|
1354
|
+
const lines = msg.split(/[\r\n]+/);
|
|
1355
|
+
const meaningfulLines = lines.filter((line) => {
|
|
1356
|
+
const trimmed = line.trim();
|
|
1357
|
+
if (!trimmed) return false;
|
|
1358
|
+
if (/^(?:Enumerating|Counting|Compressing|Writing)\s+objects:\s*\d+%/i.test(trimmed))
|
|
1359
|
+
return false;
|
|
1360
|
+
if (/^Delta compression using up to \d+ threads/i.test(trimmed)) return false;
|
|
1361
|
+
if (/^Total \d+ \(delta \d+\), reused \d+/i.test(trimmed)) return false;
|
|
1362
|
+
if (/^POST git-(?:receive|upload)-pack \([^)]+\)/i.test(trimmed)) return false;
|
|
1363
|
+
if (/^To https:\/\/github\.com/i.test(trimmed)) return false;
|
|
1364
|
+
return true;
|
|
1365
|
+
});
|
|
1366
|
+
const cleaned = meaningfulLines.join("\n").trim();
|
|
1367
|
+
if (cleaned.includes("Protected branch update failed") || cleaned.includes("GH006") || cleaned.includes("Cannot force-push to a protected branch")) {
|
|
1368
|
+
const b = branch ? ` "${branch}"` : "";
|
|
1369
|
+
return `GitHub rejected push: Protected branch policy is enabled for${b}. Force-pushing to this branch is blocked in your repository settings on GitHub.`;
|
|
1370
|
+
}
|
|
1371
|
+
if (cleaned.includes("GH001") || cleaned.includes("exceeds GitHub's file size limit")) {
|
|
1372
|
+
const match = cleaned.match(/File\s+([^\s]+)\s+is\s+([0-9.]+)\s+MB/i);
|
|
1373
|
+
if (match) {
|
|
1374
|
+
return `Large file rejected by GitHub: "${match[1]}" is ${match[2]} MB, which exceeds GitHub's 100 MB limit. Track this file with Git LFS or add it to .gitignore.`;
|
|
1375
|
+
}
|
|
1376
|
+
return `Large file rejected by GitHub: Files exceeding 100 MB must be tracked with Git LFS or excluded in .gitignore.`;
|
|
1377
|
+
}
|
|
1378
|
+
if (cleaned.includes("RPC failed") || cleaned.includes("the remote end hung up unexpectedly") || cleaned.includes("early EOF") || cleaned.includes("curl 55")) {
|
|
1379
|
+
return "Push connection interrupted or payload too large. (Buffer increased to 1GB). Check network stability or use Git LFS if pushing large binaries.";
|
|
1380
|
+
}
|
|
1381
|
+
if (cleaned.includes("terminal prompts disabled") || cleaned.includes("Authentication failed") || cleaned.includes("could not read Username") || cleaned.includes("Permission to") && cleaned.includes("denied")) {
|
|
1382
|
+
return "Authentication failed or permission denied for remote repository. Verify your GitHub account has write access.";
|
|
1383
|
+
}
|
|
1384
|
+
if (cleaned.includes("non-fast-forward") || cleaned.includes("fetch first") || cleaned.includes("Updates were rejected because the remote contains work")) {
|
|
1385
|
+
return "Push rejected: Remote repository has new incoming changes. Pull latest changes first to synchronize.";
|
|
1386
|
+
}
|
|
1387
|
+
return cleaned || "Git operation failed. Please check repository state.";
|
|
1388
|
+
}
|
|
1336
1389
|
async function pushToRemote(sessionId, remote, branch) {
|
|
1337
1390
|
const remotes = await getRemotes(sessionId);
|
|
1338
1391
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
@@ -1342,13 +1395,21 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1342
1395
|
"-c",
|
|
1343
1396
|
"credential.helper=",
|
|
1344
1397
|
"-c",
|
|
1398
|
+
"core.longpaths=true",
|
|
1399
|
+
"-c",
|
|
1345
1400
|
"lfs.allowincompletepush=true",
|
|
1346
1401
|
"-c",
|
|
1347
|
-
"pack.windowMemory=
|
|
1402
|
+
"pack.windowMemory=512m",
|
|
1348
1403
|
"-c",
|
|
1349
|
-
"pack.packSizeLimit=
|
|
1404
|
+
"pack.packSizeLimit=1024m",
|
|
1350
1405
|
"-c",
|
|
1351
|
-
"http.postBuffer=
|
|
1406
|
+
"http.postBuffer=1048576000",
|
|
1407
|
+
"-c",
|
|
1408
|
+
"http.maxRequestBuffer=1048576000",
|
|
1409
|
+
"-c",
|
|
1410
|
+
"http.lowSpeedLimit=1000",
|
|
1411
|
+
"-c",
|
|
1412
|
+
"http.lowSpeedTime=180",
|
|
1352
1413
|
"-c",
|
|
1353
1414
|
"lfs.concurrenttransfers=8",
|
|
1354
1415
|
"push",
|
|
@@ -1366,18 +1427,17 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1366
1427
|
return await doPush();
|
|
1367
1428
|
} catch (err) {
|
|
1368
1429
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1369
|
-
console.log("Push rejected because branch is behind. Auto-pulling
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1430
|
+
console.log("Push rejected because branch is behind. Auto-pulling before retry...");
|
|
1431
|
+
try {
|
|
1432
|
+
await pullFromRemote(sessionId, remote, branch);
|
|
1433
|
+
return await doPush();
|
|
1434
|
+
} catch (pullErr) {
|
|
1435
|
+
throw new Error(
|
|
1436
|
+
cleanGitErrorMessage(pullErr.message, getToken(sessionId) || void 0, branch)
|
|
1437
|
+
);
|
|
1377
1438
|
}
|
|
1378
|
-
return await doPush();
|
|
1379
1439
|
}
|
|
1380
|
-
throw err;
|
|
1440
|
+
throw new Error(cleanGitErrorMessage(err.message, getToken(sessionId) || void 0, branch));
|
|
1381
1441
|
}
|
|
1382
1442
|
}
|
|
1383
1443
|
async function masterForcePush(sessionId, remote, branch) {
|
|
@@ -1411,18 +1471,14 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1411
1471
|
}
|
|
1412
1472
|
await ensureAiWeightsUseLfs(git);
|
|
1413
1473
|
try {
|
|
1414
|
-
await git.
|
|
1474
|
+
await git.raw(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "-A"]);
|
|
1415
1475
|
} catch {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
1421
|
-
const email = await git.getConfig("user.email");
|
|
1422
|
-
if (!email.value) await git.addConfig("user.email", "auto@gitelectron.local");
|
|
1423
|
-
} catch (e) {
|
|
1424
|
-
console.warn("Failed to ensure git identity before master push:", e);
|
|
1476
|
+
try {
|
|
1477
|
+
await git.add(".");
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1425
1480
|
}
|
|
1481
|
+
await ensureGitIdentity(sessionId, git);
|
|
1426
1482
|
try {
|
|
1427
1483
|
await git.commit("Master Force Push - Sync changes");
|
|
1428
1484
|
} catch (e) {
|
|
@@ -1430,11 +1486,24 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1430
1486
|
console.warn("Commit note:", e.message);
|
|
1431
1487
|
}
|
|
1432
1488
|
}
|
|
1433
|
-
const tempGit = createSafeGit(repoPath, sessionId);
|
|
1434
1489
|
const pushArgs = [
|
|
1435
1490
|
"-c",
|
|
1436
1491
|
"credential.helper=",
|
|
1437
1492
|
"-c",
|
|
1493
|
+
"core.longpaths=true",
|
|
1494
|
+
"-c",
|
|
1495
|
+
"http.postBuffer=1048576000",
|
|
1496
|
+
"-c",
|
|
1497
|
+
"http.maxRequestBuffer=1048576000",
|
|
1498
|
+
"-c",
|
|
1499
|
+
"pack.windowMemory=512m",
|
|
1500
|
+
"-c",
|
|
1501
|
+
"pack.packSizeLimit=1024m",
|
|
1502
|
+
"-c",
|
|
1503
|
+
"http.lowSpeedLimit=1000",
|
|
1504
|
+
"-c",
|
|
1505
|
+
"http.lowSpeedTime=180",
|
|
1506
|
+
"-c",
|
|
1438
1507
|
"filter.lfs.required=false",
|
|
1439
1508
|
"-c",
|
|
1440
1509
|
"lfs.allowincompletepush=true",
|
|
@@ -1444,11 +1513,75 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1444
1513
|
"--no-verify",
|
|
1445
1514
|
"-f"
|
|
1446
1515
|
];
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1516
|
+
const targetRemoteUrl = remoteObj && remoteObj.refs?.push ? injectAuthToken(sessionId, remoteObj.refs.push) : remote || "origin";
|
|
1517
|
+
let currentActiveBranch = branch;
|
|
1518
|
+
try {
|
|
1519
|
+
const status = await git.status();
|
|
1520
|
+
if (status.current && !status.current.includes("(no") && status.current !== "HEAD") {
|
|
1521
|
+
currentActiveBranch = status.current;
|
|
1522
|
+
}
|
|
1523
|
+
} catch {
|
|
1524
|
+
}
|
|
1525
|
+
const targetBranch = branch || currentActiveBranch || "main";
|
|
1526
|
+
const pushRefSpec = currentActiveBranch ? `${currentActiveBranch}:${targetBranch}` : targetBranch;
|
|
1527
|
+
try {
|
|
1528
|
+
return await new Promise((resolve, reject) => {
|
|
1529
|
+
const child = child_process.spawn(getGitBinary(), [...pushArgs, targetRemoteUrl, pushRefSpec], {
|
|
1530
|
+
cwd: repoPath,
|
|
1531
|
+
env: {
|
|
1532
|
+
...process.env,
|
|
1533
|
+
GCM_INTERACTIVE: "false",
|
|
1534
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1535
|
+
GIT_ASKPASS: "echo"
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
let stdout = "";
|
|
1539
|
+
let stderr = "";
|
|
1540
|
+
const wc = electron.webContents.fromId(sessionId);
|
|
1541
|
+
child.stdout.on("data", (d) => {
|
|
1542
|
+
stdout += d.toString();
|
|
1543
|
+
});
|
|
1544
|
+
child.stderr.on("data", (d) => {
|
|
1545
|
+
const text = d.toString();
|
|
1546
|
+
stderr += text;
|
|
1547
|
+
const match = text.match(
|
|
1548
|
+
/(Writing objects|Compressing objects|Counting objects|Enumerating objects):\s+(\d+)%/i
|
|
1549
|
+
);
|
|
1550
|
+
if (match && wc) {
|
|
1551
|
+
const stage = match[1];
|
|
1552
|
+
const progress = parseInt(match[2], 10);
|
|
1553
|
+
try {
|
|
1554
|
+
wc.send("git-progress", { method: "push", stage, progress });
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
child.on("close", (code) => {
|
|
1560
|
+
if (code === 0) {
|
|
1561
|
+
resolve(stdout || stderr || "Force push successful");
|
|
1562
|
+
} else {
|
|
1563
|
+
const combined = (stderr + "\n" + stdout).trim();
|
|
1564
|
+
const clean = cleanGitErrorMessage(
|
|
1565
|
+
combined,
|
|
1566
|
+
getToken(sessionId) || void 0,
|
|
1567
|
+
targetBranch
|
|
1568
|
+
);
|
|
1569
|
+
reject(new Error(clean));
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
child.on("error", (err) => {
|
|
1573
|
+
reject(
|
|
1574
|
+
new Error(
|
|
1575
|
+
cleanGitErrorMessage(err.message, getToken(sessionId) || void 0, targetBranch)
|
|
1576
|
+
)
|
|
1577
|
+
);
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
throw new Error(
|
|
1582
|
+
cleanGitErrorMessage(err.message, getToken(sessionId) || void 0, targetBranch)
|
|
1583
|
+
);
|
|
1450
1584
|
}
|
|
1451
|
-
return await tempGit.raw([...pushArgs, remote || "origin", branch]);
|
|
1452
1585
|
}
|
|
1453
1586
|
async function pullFromRemote(sessionId, remote, branch) {
|
|
1454
1587
|
const { git, repoPath } = getGitSession(sessionId);
|
|
@@ -1458,14 +1591,7 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1458
1591
|
}
|
|
1459
1592
|
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1460
1593
|
const targetRemote = remoteObj.name;
|
|
1461
|
-
|
|
1462
|
-
const name = await git.getConfig("user.name");
|
|
1463
|
-
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
1464
|
-
const email = await git.getConfig("user.email");
|
|
1465
|
-
if (!email.value) await git.addConfig("user.email", "auto@gitelectron.local");
|
|
1466
|
-
} catch (e) {
|
|
1467
|
-
console.warn("Failed to ensure git identity before pull:", e);
|
|
1468
|
-
}
|
|
1594
|
+
await ensureGitIdentity(sessionId, git);
|
|
1469
1595
|
let targetBranch = branch;
|
|
1470
1596
|
if (!targetBranch) {
|
|
1471
1597
|
const status = await git.status();
|
|
@@ -1480,6 +1606,37 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1480
1606
|
}
|
|
1481
1607
|
}
|
|
1482
1608
|
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : targetRemote;
|
|
1609
|
+
let headBefore = null;
|
|
1610
|
+
try {
|
|
1611
|
+
headBefore = (await git.raw(["rev-parse", "HEAD"])).trim();
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
try {
|
|
1615
|
+
const fetchArgs = [
|
|
1616
|
+
"-c",
|
|
1617
|
+
"credential.helper=",
|
|
1618
|
+
"-c",
|
|
1619
|
+
"core.longpaths=true",
|
|
1620
|
+
"fetch",
|
|
1621
|
+
"--prune",
|
|
1622
|
+
authUrl,
|
|
1623
|
+
targetBranch
|
|
1624
|
+
];
|
|
1625
|
+
await new Promise((resolve) => {
|
|
1626
|
+
const child = child_process.spawn(getGitBinary(), fetchArgs, {
|
|
1627
|
+
cwd: repoPath,
|
|
1628
|
+
env: {
|
|
1629
|
+
...process.env,
|
|
1630
|
+
GCM_INTERACTIVE: "false",
|
|
1631
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1632
|
+
GIT_ASKPASS: "echo"
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
child.on("close", () => resolve());
|
|
1636
|
+
child.on("error", () => resolve());
|
|
1637
|
+
});
|
|
1638
|
+
} catch {
|
|
1639
|
+
}
|
|
1483
1640
|
let didStash = false;
|
|
1484
1641
|
try {
|
|
1485
1642
|
const status = await git.status();
|
|
@@ -1505,16 +1662,14 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1505
1662
|
"-c",
|
|
1506
1663
|
"core.longpaths=true",
|
|
1507
1664
|
"-c",
|
|
1508
|
-
"
|
|
1665
|
+
"http.postBuffer=1048576000",
|
|
1509
1666
|
"-c",
|
|
1510
|
-
"
|
|
1511
|
-
"-c",
|
|
1512
|
-
"http.postBuffer=524288000",
|
|
1667
|
+
"http.maxRequestBuffer=1048576000",
|
|
1513
1668
|
"pull",
|
|
1669
|
+
"--progress",
|
|
1514
1670
|
"--no-rebase",
|
|
1515
1671
|
"--allow-unrelated-histories",
|
|
1516
|
-
"
|
|
1517
|
-
"ours",
|
|
1672
|
+
"--stat",
|
|
1518
1673
|
authUrl,
|
|
1519
1674
|
targetBranch
|
|
1520
1675
|
];
|
|
@@ -1523,18 +1678,30 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1523
1678
|
cwd: repoPath,
|
|
1524
1679
|
env: {
|
|
1525
1680
|
...process.env,
|
|
1526
|
-
GIT_LFS_SKIP_SMUDGE: "1",
|
|
1527
1681
|
GCM_INTERACTIVE: "false",
|
|
1528
1682
|
GIT_TERMINAL_PROMPT: "0",
|
|
1529
1683
|
GIT_ASKPASS: "echo"
|
|
1530
1684
|
}
|
|
1531
1685
|
});
|
|
1532
1686
|
let out = "";
|
|
1687
|
+
const wc = electron.webContents.fromId(sessionId);
|
|
1533
1688
|
child.stdout.on("data", (d) => {
|
|
1534
1689
|
out += d.toString();
|
|
1535
1690
|
});
|
|
1536
1691
|
child.stderr.on("data", (d) => {
|
|
1537
|
-
|
|
1692
|
+
const text = d.toString();
|
|
1693
|
+
out += text;
|
|
1694
|
+
const match = text.match(
|
|
1695
|
+
/(Receiving objects|Resolving deltas|Compressing objects|Counting objects):\s+(\d+)%/i
|
|
1696
|
+
);
|
|
1697
|
+
if (match && wc) {
|
|
1698
|
+
const stage = match[1];
|
|
1699
|
+
const progress = parseInt(match[2], 10);
|
|
1700
|
+
try {
|
|
1701
|
+
wc.send("git-progress", { method: "pull", stage, progress });
|
|
1702
|
+
} catch {
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1538
1705
|
});
|
|
1539
1706
|
child.on("close", (code) => {
|
|
1540
1707
|
if (code === 0) {
|
|
@@ -1553,15 +1720,30 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1553
1720
|
}
|
|
1554
1721
|
}
|
|
1555
1722
|
const alreadyUpToDate = pullOutput.includes("Already up to date.") || pullOutput.includes("Already up-to-date.");
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const
|
|
1561
|
-
if (
|
|
1723
|
+
let changedFiles2 = [];
|
|
1724
|
+
if (headBefore && !alreadyUpToDate) {
|
|
1725
|
+
try {
|
|
1726
|
+
const diffTree = await git.raw(["diff", "--name-only", headBefore, "HEAD"]);
|
|
1727
|
+
const files = diffTree.split(/[\r\n]+/).map((f) => f.trim()).filter(Boolean);
|
|
1728
|
+
if (files.length > 0) {
|
|
1729
|
+
changedFiles2 = files;
|
|
1730
|
+
}
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (changedFiles2.length === 0 && !alreadyUpToDate) {
|
|
1735
|
+
const lines = pullOutput.split(/[\r\n]+/).map((l) => l.trim());
|
|
1736
|
+
for (const l of lines) {
|
|
1737
|
+
if (l.includes("|") && (l.includes("+") || l.includes("-"))) {
|
|
1738
|
+
const filePart = l.split("|")[0].trim();
|
|
1739
|
+
if (filePart) changedFiles2.push(filePart);
|
|
1740
|
+
}
|
|
1562
1741
|
}
|
|
1563
1742
|
}
|
|
1564
|
-
const
|
|
1743
|
+
const summaryMatch = pullOutput.match(/(\d+)\s+files?\s+changed/i);
|
|
1744
|
+
const totalFilesChanged = summaryMatch ? parseInt(summaryMatch[1], 10) : changedFiles2.length;
|
|
1745
|
+
const displayCount = Math.max(totalFilesChanged, changedFiles2.length);
|
|
1746
|
+
const message = alreadyUpToDate ? "Already up to date with remote." : displayCount > 0 ? `Successfully pulled ${displayCount} file(s) from team!` : "Successfully pulled latest changes from team!";
|
|
1565
1747
|
return {
|
|
1566
1748
|
success: true,
|
|
1567
1749
|
alreadyUpToDate,
|
|
@@ -1576,12 +1758,11 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1576
1758
|
} catch {
|
|
1577
1759
|
}
|
|
1578
1760
|
}
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
cleanMsg = cleanMsg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
|
|
1761
|
+
const cleanMsg = cleanGitErrorMessage(
|
|
1762
|
+
err?.message || String(err),
|
|
1763
|
+
getToken(sessionId) || void 0,
|
|
1764
|
+
targetBranch
|
|
1765
|
+
);
|
|
1585
1766
|
throw new Error(cleanMsg);
|
|
1586
1767
|
}
|
|
1587
1768
|
}
|
|
@@ -1629,6 +1810,111 @@ async function checkRemoteUpdates(sessionId) {
|
|
|
1629
1810
|
return { behind: 0, ahead: 0, current: "" };
|
|
1630
1811
|
}
|
|
1631
1812
|
}
|
|
1813
|
+
async function masterForcePull(sessionId, remote, branch) {
|
|
1814
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1815
|
+
const remotes = await getRemotes(sessionId);
|
|
1816
|
+
if (!remotes || remotes.length === 0) {
|
|
1817
|
+
throw new Error("No remote repository configured to force pull from.");
|
|
1818
|
+
}
|
|
1819
|
+
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1820
|
+
const targetRemote = remoteObj.name;
|
|
1821
|
+
await ensureGitIdentity(sessionId, git);
|
|
1822
|
+
let targetBranch = branch;
|
|
1823
|
+
if (!targetBranch) {
|
|
1824
|
+
const status = await git.status();
|
|
1825
|
+
targetBranch = status.current || "main";
|
|
1826
|
+
if (targetBranch.includes("(no") || targetBranch === "HEAD") {
|
|
1827
|
+
try {
|
|
1828
|
+
const remoteHead = await git.raw(["rev-parse", "--abbrev-ref", `${targetRemote}/HEAD`]);
|
|
1829
|
+
targetBranch = remoteHead.trim().replace(`${targetRemote}/`, "");
|
|
1830
|
+
} catch {
|
|
1831
|
+
targetBranch = "main";
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : targetRemote;
|
|
1836
|
+
let headBefore = null;
|
|
1837
|
+
try {
|
|
1838
|
+
headBefore = (await git.raw(["rev-parse", "HEAD"])).trim();
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1841
|
+
try {
|
|
1842
|
+
const fetchArgs = [
|
|
1843
|
+
"-c",
|
|
1844
|
+
"credential.helper=",
|
|
1845
|
+
"-c",
|
|
1846
|
+
"core.longpaths=true",
|
|
1847
|
+
"-c",
|
|
1848
|
+
"http.postBuffer=1048576000",
|
|
1849
|
+
"-c",
|
|
1850
|
+
"http.maxRequestBuffer=1048576000",
|
|
1851
|
+
"fetch",
|
|
1852
|
+
"--progress",
|
|
1853
|
+
"--prune",
|
|
1854
|
+
authUrl,
|
|
1855
|
+
targetBranch
|
|
1856
|
+
];
|
|
1857
|
+
await new Promise((resolve, reject) => {
|
|
1858
|
+
const child = child_process.spawn(getGitBinary(), fetchArgs, {
|
|
1859
|
+
cwd: repoPath,
|
|
1860
|
+
env: {
|
|
1861
|
+
...process.env,
|
|
1862
|
+
GCM_INTERACTIVE: "false",
|
|
1863
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1864
|
+
GIT_ASKPASS: "echo"
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
let errOut = "";
|
|
1868
|
+
const wc = electron.webContents.fromId(sessionId);
|
|
1869
|
+
child.stderr.on("data", (d) => {
|
|
1870
|
+
const text = d.toString();
|
|
1871
|
+
errOut += text;
|
|
1872
|
+
const match = text.match(
|
|
1873
|
+
/(Receiving objects|Resolving deltas|Compressing objects|Counting objects):\s+(\d+)%/i
|
|
1874
|
+
);
|
|
1875
|
+
if (match && wc) {
|
|
1876
|
+
const stage = match[1];
|
|
1877
|
+
const progress = parseInt(match[2], 10);
|
|
1878
|
+
try {
|
|
1879
|
+
wc.send("git-progress", { method: "pull", stage, progress });
|
|
1880
|
+
} catch {
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
child.on("close", (code) => {
|
|
1885
|
+
if (code === 0) resolve();
|
|
1886
|
+
else reject(new Error(errOut || `Fetch failed with exit code ${code}`));
|
|
1887
|
+
});
|
|
1888
|
+
child.on("error", (err) => reject(err));
|
|
1889
|
+
});
|
|
1890
|
+
await git.reset(["--hard", "FETCH_HEAD"]);
|
|
1891
|
+
try {
|
|
1892
|
+
await git.clean("f", ["-d"]);
|
|
1893
|
+
} catch {
|
|
1894
|
+
}
|
|
1895
|
+
let changedFiles2 = [];
|
|
1896
|
+
if (headBefore) {
|
|
1897
|
+
try {
|
|
1898
|
+
const diffTree = await git.raw(["diff", "--name-only", headBefore, "HEAD"]);
|
|
1899
|
+
changedFiles2 = diffTree.split(/[\r\n]+/).map((f) => f.trim()).filter(Boolean);
|
|
1900
|
+
} catch {
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
const message = changedFiles2.length > 0 ? `Force Pull successful! Synced ${changedFiles2.length} file(s) from GitHub.` : "Force Pull successful! All files are perfectly matched with GitHub.";
|
|
1904
|
+
return {
|
|
1905
|
+
success: true,
|
|
1906
|
+
message,
|
|
1907
|
+
filesChanged: changedFiles2
|
|
1908
|
+
};
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
const cleanMsg = cleanGitErrorMessage(
|
|
1911
|
+
err?.message || String(err),
|
|
1912
|
+
getToken(sessionId) || void 0,
|
|
1913
|
+
targetBranch
|
|
1914
|
+
);
|
|
1915
|
+
throw new Error(cleanMsg);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1632
1918
|
async function resetToRemote(sessionId, remote, branch) {
|
|
1633
1919
|
const { git } = getGitSession(sessionId);
|
|
1634
1920
|
const remotes = await getRemotes(sessionId);
|
|
@@ -2325,7 +2611,7 @@ const GITHUB_MODELS_CONFIG = {
|
|
|
2325
2611
|
const CIPHER_ALGO = "aes-256-cbc";
|
|
2326
2612
|
const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
|
|
2327
2613
|
const CIPHER_SALT = "salt_gitelectron";
|
|
2328
|
-
const ENCRYPTED_BACKUP_PAYLOAD = "
|
|
2614
|
+
const ENCRYPTED_BACKUP_PAYLOAD = "19957ea42600d64c44b82d056696e39a:ff16f32b11fb7ea9962dcf348513f3228bba4e48e217bcd75235cea22d63a1bc874fc3e3ef3baa0c7edaaf0bcedafa54e582131e1b09478edfb80218707f92c0";
|
|
2329
2615
|
function getBackupApiKeys() {
|
|
2330
2616
|
try {
|
|
2331
2617
|
const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
|
|
@@ -2364,7 +2650,12 @@ Return the response strictly as a JSON object with this shape, and absolutely no
|
|
|
2364
2650
|
"commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
|
|
2365
2651
|
}`;
|
|
2366
2652
|
async function generateWithGemini(genAI, contents) {
|
|
2367
|
-
const models = [
|
|
2653
|
+
const models = [
|
|
2654
|
+
"gemini-flash-lite-latest",
|
|
2655
|
+
"gemini-3.5-flash-lite",
|
|
2656
|
+
"gemini-flash-latest",
|
|
2657
|
+
"gemini-3.8-flash"
|
|
2658
|
+
];
|
|
2368
2659
|
let lastErr = null;
|
|
2369
2660
|
for (const modelName of models) {
|
|
2370
2661
|
try {
|
|
@@ -2378,6 +2669,19 @@ async function generateWithGemini(genAI, contents) {
|
|
|
2378
2669
|
}
|
|
2379
2670
|
throw lastErr || new Error("All Gemini model fallbacks failed");
|
|
2380
2671
|
}
|
|
2672
|
+
function extractJson(text) {
|
|
2673
|
+
const cleaned = text.replace(/```json/gi, "").replace(/```/g, "").trim();
|
|
2674
|
+
try {
|
|
2675
|
+
return JSON.parse(cleaned);
|
|
2676
|
+
} catch {
|
|
2677
|
+
const firstBrace = cleaned.indexOf("{");
|
|
2678
|
+
const lastBrace = cleaned.lastIndexOf("}");
|
|
2679
|
+
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
2680
|
+
return JSON.parse(cleaned.substring(firstBrace, lastBrace + 1));
|
|
2681
|
+
}
|
|
2682
|
+
throw new Error("No valid JSON object found in response");
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2381
2685
|
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2382
2686
|
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2383
2687
|
const prompt = `
|
|
@@ -2405,9 +2709,8 @@ ${errorMsg}
|
|
|
2405
2709
|
],
|
|
2406
2710
|
response_format: { type: "json_object" }
|
|
2407
2711
|
});
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
const parsed = JSON.parse(responseText);
|
|
2712
|
+
const responseText = response.choices[0].message.content || "{}";
|
|
2713
|
+
const parsed = extractJson(responseText);
|
|
2411
2714
|
if (parsed.explanation && Array.isArray(parsed.commandsToRun)) {
|
|
2412
2715
|
console.log("[AI Service] Successfully used GitHub Copilot Models API!");
|
|
2413
2716
|
return parsed;
|
|
@@ -2440,8 +2743,7 @@ ${errorMsg}
|
|
|
2440
2743
|
} else {
|
|
2441
2744
|
throw new Error(`Unsupported AI Provider: ${provider}`);
|
|
2442
2745
|
}
|
|
2443
|
-
|
|
2444
|
-
const parsed = JSON.parse(responseText);
|
|
2746
|
+
const parsed = extractJson(responseText);
|
|
2445
2747
|
if (!parsed.explanation || !Array.isArray(parsed.commandsToRun)) {
|
|
2446
2748
|
throw new Error("AI returned an invalid response format.");
|
|
2447
2749
|
}
|
|
@@ -2560,9 +2862,8 @@ ${content}
|
|
|
2560
2862
|
],
|
|
2561
2863
|
response_format: { type: "json_object" }
|
|
2562
2864
|
});
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
const parsed = JSON.parse(responseText);
|
|
2865
|
+
const responseText = response.choices[0].message.content || "{}";
|
|
2866
|
+
const parsed = extractJson(responseText);
|
|
2566
2867
|
if (parsed.reason && parsed.proposedFix) {
|
|
2567
2868
|
return parsed;
|
|
2568
2869
|
}
|
|
@@ -2580,8 +2881,7 @@ ${content}
|
|
|
2580
2881
|
{ text: prompt }
|
|
2581
2882
|
]);
|
|
2582
2883
|
}
|
|
2583
|
-
|
|
2584
|
-
const parsed = JSON.parse(responseText);
|
|
2884
|
+
const parsed = extractJson(responseText);
|
|
2585
2885
|
if (!parsed.reason || !parsed.proposedFix) {
|
|
2586
2886
|
throw new Error("AI returned an invalid response format.");
|
|
2587
2887
|
}
|
|
@@ -2733,7 +3033,7 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2733
3033
|
}
|
|
2734
3034
|
} catch {
|
|
2735
3035
|
}
|
|
2736
|
-
},
|
|
3036
|
+
}, 1e4);
|
|
2737
3037
|
currentWatcher = chokidar.watch(repoPath, {
|
|
2738
3038
|
ignored: [
|
|
2739
3039
|
/(^|[/\\])\.git([/\\]|$)/,
|
|
@@ -2832,14 +3132,18 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2832
3132
|
if (globalOnErrorApproval && !isResolving) {
|
|
2833
3133
|
isResolving = true;
|
|
2834
3134
|
onLog("Attempting to resolve push failure...");
|
|
2835
|
-
handleAutoSyncError(sessionId, error.message, onLog).catch(
|
|
2836
|
-
|
|
2837
|
-
)
|
|
3135
|
+
handleAutoSyncError(sessionId, error.message, onLog).catch((err) => {
|
|
3136
|
+
onLog(`Resolution failed: ${err.message}`);
|
|
3137
|
+
}).finally(() => {
|
|
3138
|
+
setTimeout(() => {
|
|
3139
|
+
isResolving = false;
|
|
3140
|
+
}, 3e4);
|
|
3141
|
+
});
|
|
2838
3142
|
}
|
|
2839
3143
|
}
|
|
2840
3144
|
}
|
|
2841
3145
|
async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
2842
|
-
const {
|
|
3146
|
+
const { repoPath } = getGitSession(sessionId);
|
|
2843
3147
|
if (!repoPath) {
|
|
2844
3148
|
isResolving = false;
|
|
2845
3149
|
return;
|
|
@@ -2855,31 +3159,32 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2855
3159
|
return;
|
|
2856
3160
|
}
|
|
2857
3161
|
try {
|
|
2858
|
-
onLog("
|
|
3162
|
+
onLog("Syncing incoming remote changes from team...");
|
|
2859
3163
|
const status2 = await getStatus(sessionId);
|
|
2860
3164
|
const currentBranch = status2.current || "main";
|
|
2861
|
-
await
|
|
2862
|
-
|
|
2863
|
-
"--autostash": null,
|
|
2864
|
-
"-X": "ours"
|
|
2865
|
-
});
|
|
2866
|
-
onLog("Pull successful, attempting push again...");
|
|
3165
|
+
const pullResult = await pullFromRemote(sessionId, "origin", currentBranch);
|
|
3166
|
+
onLog(`Pull result: ${pullResult.message}. Resuming push...`);
|
|
2867
3167
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2868
3168
|
onLog("✅ Error resolved and push resumed!");
|
|
2869
3169
|
isResolving = false;
|
|
2870
3170
|
if (globalOnResolveComplete) globalOnResolveComplete();
|
|
2871
3171
|
return;
|
|
2872
3172
|
} catch (pullErr) {
|
|
2873
|
-
onLog(`Pull
|
|
3173
|
+
onLog(`Pull notice: ${pullErr.message}`);
|
|
2874
3174
|
}
|
|
2875
3175
|
const status = await getStatus(sessionId);
|
|
2876
3176
|
if (status.conflicted.length === 0) {
|
|
2877
|
-
onLog("
|
|
3177
|
+
onLog("Auto-push paused: Remote rejected push or requires review.");
|
|
2878
3178
|
isResolving = false;
|
|
2879
3179
|
if (globalOnErrorApproval) {
|
|
3180
|
+
const cleanReason = cleanGitErrorMessage(
|
|
3181
|
+
errorMsg,
|
|
3182
|
+
getToken(sessionId) || void 0,
|
|
3183
|
+
status.current || "main"
|
|
3184
|
+
);
|
|
2880
3185
|
globalOnErrorApproval({
|
|
2881
|
-
reason:
|
|
2882
|
-
proposedFix: "
|
|
3186
|
+
reason: cleanReason || "Remote repository rejected the push request.",
|
|
3187
|
+
proposedFix: 'Click "Approve & Fix" to attempt auto-reconciliation and push your changes.',
|
|
2883
3188
|
files: []
|
|
2884
3189
|
});
|
|
2885
3190
|
}
|
|
@@ -2931,7 +3236,8 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2931
3236
|
if (!repoPath) return false;
|
|
2932
3237
|
if (globalOnLog) globalOnLog("Applying AI resolution to conflicted files...");
|
|
2933
3238
|
try {
|
|
2934
|
-
|
|
3239
|
+
const hasResolvedFiles = resolvedCode && Object.keys(resolvedCode).length > 0;
|
|
3240
|
+
if (hasResolvedFiles) {
|
|
2935
3241
|
for (const [file, content] of Object.entries(resolvedCode)) {
|
|
2936
3242
|
await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
|
|
2937
3243
|
try {
|
|
@@ -2952,14 +3258,20 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2952
3258
|
}
|
|
2953
3259
|
}
|
|
2954
3260
|
}
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3261
|
+
try {
|
|
3262
|
+
await git.rebase({ "--continue": null });
|
|
3263
|
+
} catch {
|
|
3264
|
+
await ensureGitIdentity(sessionId, git);
|
|
3265
|
+
await git.commit("Auto-resolved merge conflict by AI");
|
|
3266
|
+
}
|
|
3267
|
+
} else {
|
|
3268
|
+
try {
|
|
3269
|
+
await pullFromRemote(sessionId);
|
|
3270
|
+
} catch {
|
|
3271
|
+
}
|
|
2960
3272
|
}
|
|
2961
3273
|
const status = await getStatus(sessionId);
|
|
2962
|
-
if (globalOnLog) globalOnLog("Pushing
|
|
3274
|
+
if (globalOnLog) globalOnLog("Pushing changes...");
|
|
2963
3275
|
await pushToRemote(sessionId, "origin", status.current || "main");
|
|
2964
3276
|
if (globalOnLog) globalOnLog("✅ Push successful!");
|
|
2965
3277
|
isResolving = false;
|
|
@@ -3241,6 +3553,9 @@ electron.app.whenReady().then(async () => {
|
|
|
3241
3553
|
electron.ipcMain.handle("master-push", async (event, remote, branch) => {
|
|
3242
3554
|
return await masterForcePush(event.sender.id, remote, branch);
|
|
3243
3555
|
});
|
|
3556
|
+
electron.ipcMain.handle("master-pull", async (event, remote, branch) => {
|
|
3557
|
+
return await masterForcePull(event.sender.id, remote, branch);
|
|
3558
|
+
});
|
|
3244
3559
|
electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
|
|
3245
3560
|
return await pullFromRemote(event.sender.id, remote, branch);
|
|
3246
3561
|
});
|