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/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
- const name = await git.getConfig("user.name");
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
- try {
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=256m",
1402
+ "pack.windowMemory=512m",
1348
1403
  "-c",
1349
- "pack.packSizeLimit=500m",
1404
+ "pack.packSizeLimit=1024m",
1350
1405
  "-c",
1351
- "http.postBuffer=524288000",
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 with rebase...");
1370
- const { git } = getGitSession(sessionId);
1371
- const options = { "--rebase": "false", "--autostash": null, "-X": "ours" };
1372
- if (remoteObj && remoteObj.refs.fetch) {
1373
- const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
1374
- await git.pull(authUrl, branch, options);
1375
- } else {
1376
- await git.pull(remote, branch, options);
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.add(".");
1474
+ await git.raw(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "-A"]);
1415
1475
  } catch {
1416
- await git.raw(["add", "-A"]);
1417
- }
1418
- try {
1419
- const name = await git.getConfig("user.name");
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
- if (remoteObj && remoteObj.refs?.push) {
1448
- const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
1449
- return await tempGit.raw([...pushArgs, authUrl, branch]);
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
- try {
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
- "filter.lfs.required=false",
1665
+ "http.postBuffer=1048576000",
1509
1666
  "-c",
1510
- "filter.lfs.smudge=",
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
- "-X",
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
- out += d.toString();
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
- const lines = pullOutput.split(/[\r\n]+/).map((l) => l.trim());
1557
- const changedFiles2 = [];
1558
- for (const l of lines) {
1559
- if (l.includes("|") && (l.includes("+") || l.includes("-"))) {
1560
- const filePart = l.split("|")[0].trim();
1561
- if (filePart) changedFiles2.push(filePart);
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 message = alreadyUpToDate ? "Already up to date with remote." : changedFiles2.length > 0 ? `Successfully pulled ${changedFiles2.length} file(s) from team!` : "Successfully pulled latest changes from team!";
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 token = getToken(sessionId);
1580
- let cleanMsg = err?.message || String(err);
1581
- if (token) {
1582
- cleanMsg = cleanMsg.split(token).join("***");
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 = "1aa43686251fc27545934564c92ebcd9:acac927d94c7479c0df45b9245f61f47bd4184fca2323e6ce971bc5c74dffaac925caa81788ce81374a05c7072301ee2eac10531be99a850453ff3fd23ea21cd11a8fa95d07d19fe1a8abcdd9a282b06d3e391a621137027c03765f65e977ed432c00e0e9627b2dad77ca708273eff14eac1bbfd01085d4b645a271167bae46edec2a09491b877fba73c3d29dc2e0e2120785347c9189db6bc9892a6018b6dea7ac064b991e9687e2df56e983743985f";
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 = ["gemini-2.0-flash", "gemini-1.5-flash-latest", "gemini-1.5-flash"];
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
- let responseText = response.choices[0].message.content || "{}";
2409
- responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
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
- responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
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
- let responseText = response.choices[0].message.content || "{}";
2564
- responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
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
- responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
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
- }, 25e3);
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
- (err) => onLog(`Resolution failed: ${err.message}`)
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 { git, repoPath } = getGitSession(sessionId);
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("Pulling remote changes with rebase...");
3162
+ onLog("Syncing incoming remote changes from team...");
2859
3163
  const status2 = await getStatus(sessionId);
2860
3164
  const currentBranch = status2.current || "main";
2861
- await git.pull("origin", currentBranch, {
2862
- "--rebase": "false",
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 failed (possible conflict): ${pullErr.message}`);
3173
+ onLog(`Pull notice: ${pullErr.message}`);
2874
3174
  }
2875
3175
  const status = await getStatus(sessionId);
2876
3176
  if (status.conflicted.length === 0) {
2877
- onLog("No conflicts found but resolution failed. Giving up.");
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: "Failed to resolve error automatically. No file conflicts found.",
2882
- proposedFix: "Manual intervention required.",
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
- if (resolvedCode) {
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
- try {
2957
- await git.rebase({ "--continue": null });
2958
- } catch {
2959
- await git.commit("Auto-resolved merge conflict by AI");
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 AI-resolved changes...");
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
  });