gitxelectron 1.3.8 → 1.4.1
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/bin/ai-setup.js +46 -0
- package/bin/cli.js +35 -13
- package/out/main/index.js +308 -252
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/index-CvICUEsw.css +2989 -0
- package/out/renderer/assets/{index-FVTFdxtU.js → index-DypwT1IG.js} +1528 -350
- package/out/renderer/index.html +2 -2
- package/package.json +2 -1
- package/out/renderer/assets/index-D_wJ7hR7.css +0 -2989
package/out/main/index.js
CHANGED
|
@@ -111,8 +111,24 @@ async function logout(sessionId) {
|
|
|
111
111
|
}
|
|
112
112
|
async function getAuthStatus(sessionId) {
|
|
113
113
|
const activeAccount = getActiveAccount(sessionId);
|
|
114
|
-
if (!activeAccount)
|
|
115
|
-
|
|
114
|
+
if (!activeAccount) {
|
|
115
|
+
return {
|
|
116
|
+
status: "Not Authenticated",
|
|
117
|
+
username: null,
|
|
118
|
+
avatarUrl: null,
|
|
119
|
+
authMethod: "Local Git Credentials",
|
|
120
|
+
aiProvider: "GitHub Models",
|
|
121
|
+
securityStatus: "Encrypted Local Keystore"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
status: "Verified",
|
|
126
|
+
username: activeAccount.username,
|
|
127
|
+
avatarUrl: activeAccount.avatar_url || null,
|
|
128
|
+
authMethod: "GitHub OAuth (Verified)",
|
|
129
|
+
aiProvider: "GitHub Models (gpt-4o-mini)",
|
|
130
|
+
securityStatus: "End-to-End Encrypted Session"
|
|
131
|
+
};
|
|
116
132
|
}
|
|
117
133
|
async function startDeviceFlow(_sessionId) {
|
|
118
134
|
const response = await fetch("https://github.com/login/device/code", {
|
|
@@ -601,7 +617,7 @@ function initGit(sessionId, baseDir) {
|
|
|
601
617
|
try {
|
|
602
618
|
const wc = electron.webContents.fromId(sessionId);
|
|
603
619
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
604
|
-
} catch
|
|
620
|
+
} catch {
|
|
605
621
|
}
|
|
606
622
|
}
|
|
607
623
|
};
|
|
@@ -612,6 +628,20 @@ function initGit(sessionId, baseDir) {
|
|
|
612
628
|
GIT_ASKPASS: "echo"
|
|
613
629
|
});
|
|
614
630
|
gitSessions.set(sessionId, { git, repoPath: baseDir });
|
|
631
|
+
(async () => {
|
|
632
|
+
try {
|
|
633
|
+
const name = await git.getConfig("user.name");
|
|
634
|
+
if (!name.value) {
|
|
635
|
+
await git.addConfig("user.name", "GitElectron User");
|
|
636
|
+
}
|
|
637
|
+
const email = await git.getConfig("user.email");
|
|
638
|
+
if (!email.value) {
|
|
639
|
+
await git.addConfig("user.email", "auto@gitelectron.local");
|
|
640
|
+
}
|
|
641
|
+
} catch (e) {
|
|
642
|
+
console.warn("Failed to ensure git identity:", e);
|
|
643
|
+
}
|
|
644
|
+
})();
|
|
615
645
|
}
|
|
616
646
|
function createSafeGit(baseDir, sessionId) {
|
|
617
647
|
const options = {
|
|
@@ -628,7 +658,7 @@ function createSafeGit(baseDir, sessionId) {
|
|
|
628
658
|
try {
|
|
629
659
|
const wc = electron.webContents.fromId(sessionId);
|
|
630
660
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
631
|
-
} catch
|
|
661
|
+
} catch {
|
|
632
662
|
}
|
|
633
663
|
}
|
|
634
664
|
};
|
|
@@ -687,9 +717,9 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
687
717
|
let existing = "";
|
|
688
718
|
try {
|
|
689
719
|
existing = await fs__namespace.readFile(attrsPath, "utf8");
|
|
690
|
-
} catch
|
|
720
|
+
} catch {
|
|
691
721
|
}
|
|
692
|
-
|
|
722
|
+
const newLines = [];
|
|
693
723
|
for (const ext of DEFAULT_LFS_EXTENSIONS) {
|
|
694
724
|
if (!existing.includes(ext)) {
|
|
695
725
|
newLines.push(`${ext} filter=lfs diff=lfs merge=lfs -text`);
|
|
@@ -699,7 +729,7 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
699
729
|
const appendContent = (existing && !existing.endsWith("\n") ? "\n" : "") + newLines.join("\n") + "\n";
|
|
700
730
|
await fs__namespace.appendFile(attrsPath, appendContent);
|
|
701
731
|
}
|
|
702
|
-
} catch
|
|
732
|
+
} catch {
|
|
703
733
|
}
|
|
704
734
|
}
|
|
705
735
|
async function isGitRepo(dir) {
|
|
@@ -829,6 +859,7 @@ async function getStatus(sessionId) {
|
|
|
829
859
|
async function stageFiles(sessionId, files) {
|
|
830
860
|
const { git, repoPath } = getGitSession(sessionId);
|
|
831
861
|
const fileArray = Array.isArray(files) ? files : [files];
|
|
862
|
+
await ensureAiWeightsUseLfs(git);
|
|
832
863
|
if (repoPath) {
|
|
833
864
|
const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
|
|
834
865
|
const largeFiles = [];
|
|
@@ -839,7 +870,7 @@ async function stageFiles(sessionId, files) {
|
|
|
839
870
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
840
871
|
largeFiles.push(file);
|
|
841
872
|
}
|
|
842
|
-
} catch
|
|
873
|
+
} catch {
|
|
843
874
|
}
|
|
844
875
|
}
|
|
845
876
|
if (largeFiles.length > 0) {
|
|
@@ -893,6 +924,7 @@ async function unstageFiles(sessionId, files) {
|
|
|
893
924
|
}
|
|
894
925
|
async function commitChanges(sessionId, message) {
|
|
895
926
|
const { git } = getGitSession(sessionId);
|
|
927
|
+
await ensureAiWeightsUseLfs(git);
|
|
896
928
|
try {
|
|
897
929
|
const nameConfig = await git.getConfig("user.name");
|
|
898
930
|
const emailConfig = await git.getConfig("user.email");
|
|
@@ -922,7 +954,7 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
922
954
|
parsed.password = token;
|
|
923
955
|
return parsed.toString();
|
|
924
956
|
}
|
|
925
|
-
} catch
|
|
957
|
+
} catch {
|
|
926
958
|
if (remoteUrl.includes("github.com")) {
|
|
927
959
|
return remoteUrl.replace(
|
|
928
960
|
/https:\/\/(?:[^@]+@)?github\.com\//,
|
|
@@ -933,7 +965,8 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
933
965
|
return remoteUrl;
|
|
934
966
|
}
|
|
935
967
|
async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3, onProgress, onDetailedProgress) {
|
|
936
|
-
const { repoPath } = getGitSession(sessionId);
|
|
968
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
969
|
+
await ensureAiWeightsUseLfs(git);
|
|
937
970
|
if (onProgress) onProgress("Starting rapid file count to prepare massive chunking...");
|
|
938
971
|
const totalFiles = await new Promise((resolve) => {
|
|
939
972
|
const child2 = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
|
|
@@ -951,7 +984,6 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
951
984
|
return true;
|
|
952
985
|
}
|
|
953
986
|
if (onProgress) onProgress(`Total files to push: ${totalFiles}. Starting chunked upload...`);
|
|
954
|
-
const { git } = getGitSession(sessionId);
|
|
955
987
|
const remotes = await getRemotes(sessionId);
|
|
956
988
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
957
989
|
if (onProgress) onProgress("Starting massive repository push...");
|
|
@@ -1004,7 +1036,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1004
1036
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
1005
1037
|
largeFiles.push(file);
|
|
1006
1038
|
}
|
|
1007
|
-
} catch
|
|
1039
|
+
} catch {
|
|
1008
1040
|
}
|
|
1009
1041
|
})
|
|
1010
1042
|
);
|
|
@@ -1041,7 +1073,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1041
1073
|
} finally {
|
|
1042
1074
|
try {
|
|
1043
1075
|
await fs__namespace.unlink(tempFile);
|
|
1044
|
-
} catch
|
|
1076
|
+
} catch {
|
|
1045
1077
|
}
|
|
1046
1078
|
}
|
|
1047
1079
|
};
|
|
@@ -1069,7 +1101,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1069
1101
|
if (onProgress) onProgress("Committing all files...");
|
|
1070
1102
|
try {
|
|
1071
1103
|
await git.commit(`Auto-sync (Massive Push)`);
|
|
1072
|
-
} catch
|
|
1104
|
+
} catch {
|
|
1073
1105
|
}
|
|
1074
1106
|
if (onProgress) onProgress("Pushing to remote...");
|
|
1075
1107
|
const pushArgs = [
|
|
@@ -1094,7 +1126,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1094
1126
|
} catch (err) {
|
|
1095
1127
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1096
1128
|
if (onProgress) onProgress("Remote branch is ahead. Pulling changes...");
|
|
1097
|
-
await git.pull(authUrl, branch, { "--rebase":
|
|
1129
|
+
await git.pull(authUrl, branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1098
1130
|
if (onProgress) onProgress("Pushing to remote after pull...");
|
|
1099
1131
|
await git.raw([...pushArgs, authUrl, branch]);
|
|
1100
1132
|
} else {
|
|
@@ -1148,7 +1180,7 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1148
1180
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1149
1181
|
console.log("Push rejected because branch is behind. Auto-pulling with rebase...");
|
|
1150
1182
|
const { git } = getGitSession(sessionId);
|
|
1151
|
-
const options = { "--rebase":
|
|
1183
|
+
const options = { "--rebase": "false", "--autostash": null, "-X": "ours" };
|
|
1152
1184
|
if (remoteObj && remoteObj.refs.fetch) {
|
|
1153
1185
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
|
|
1154
1186
|
await git.pull(authUrl, branch, options);
|
|
@@ -1160,6 +1192,85 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1160
1192
|
throw err;
|
|
1161
1193
|
}
|
|
1162
1194
|
}
|
|
1195
|
+
async function masterForcePush(sessionId, remote, branch) {
|
|
1196
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1197
|
+
const remotes = await getRemotes(sessionId);
|
|
1198
|
+
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1199
|
+
try {
|
|
1200
|
+
const branchSummary = await git.branchLocal();
|
|
1201
|
+
let current = branchSummary.current || branch;
|
|
1202
|
+
if (current && current.startsWith("__master_push_temp_")) {
|
|
1203
|
+
const candidates = ["main", "master", branch].filter(
|
|
1204
|
+
(b) => b && !b.startsWith("__master_push_temp_") && branchSummary.all.includes(b)
|
|
1205
|
+
);
|
|
1206
|
+
const fallbackBranch = candidates[0] || "main";
|
|
1207
|
+
try {
|
|
1208
|
+
await git.checkout(fallbackBranch);
|
|
1209
|
+
current = fallbackBranch;
|
|
1210
|
+
branch = fallbackBranch;
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
for (const b of branchSummary.all) {
|
|
1215
|
+
if (b.startsWith("__master_push_temp_") && b !== current) {
|
|
1216
|
+
try {
|
|
1217
|
+
await git.deleteLocalBranch(b, true);
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} catch {
|
|
1223
|
+
}
|
|
1224
|
+
await ensureAiWeightsUseLfs(git);
|
|
1225
|
+
try {
|
|
1226
|
+
await git.add(".");
|
|
1227
|
+
} catch {
|
|
1228
|
+
await git.raw(["add", "-A"]);
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
const name = await git.getConfig("user.name");
|
|
1232
|
+
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
1233
|
+
const email = await git.getConfig("user.email");
|
|
1234
|
+
if (!email.value) await git.addConfig("user.email", "auto@gitelectron.local");
|
|
1235
|
+
} catch (e) {
|
|
1236
|
+
console.warn("Failed to ensure git identity before master push:", e);
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
await git.commit("Master Force Push - Sync changes");
|
|
1240
|
+
} catch (e) {
|
|
1241
|
+
if (!e.message.includes("nothing to commit")) {
|
|
1242
|
+
console.warn("Commit note:", e.message);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const tempGit = simpleGit.simpleGit({
|
|
1246
|
+
baseDir: repoPath,
|
|
1247
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1248
|
+
}).env({
|
|
1249
|
+
...process.env,
|
|
1250
|
+
GCM_INTERACTIVE: "false",
|
|
1251
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1252
|
+
GIT_ASKPASS: "echo",
|
|
1253
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
1254
|
+
GIT_LFS_SKIP_PUSH: "1"
|
|
1255
|
+
});
|
|
1256
|
+
const pushArgs = [
|
|
1257
|
+
"-c",
|
|
1258
|
+
"credential.helper=",
|
|
1259
|
+
"-c",
|
|
1260
|
+
"filter.lfs.required=false",
|
|
1261
|
+
"-c",
|
|
1262
|
+
"lfs.allowincompletepush=true",
|
|
1263
|
+
"push",
|
|
1264
|
+
"--progress",
|
|
1265
|
+
"--no-verify",
|
|
1266
|
+
"-f"
|
|
1267
|
+
];
|
|
1268
|
+
if (remoteObj && remoteObj.refs?.push) {
|
|
1269
|
+
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1270
|
+
return await tempGit.raw([...pushArgs, authUrl, branch]);
|
|
1271
|
+
}
|
|
1272
|
+
return await tempGit.raw([...pushArgs, remote || "origin", branch]);
|
|
1273
|
+
}
|
|
1163
1274
|
async function pullFromRemote(sessionId, remote, branch) {
|
|
1164
1275
|
const { git, repoPath } = getGitSession(sessionId);
|
|
1165
1276
|
const remotes = await getRemotes(sessionId);
|
|
@@ -1168,6 +1279,14 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1168
1279
|
}
|
|
1169
1280
|
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1170
1281
|
const targetRemote = remoteObj.name;
|
|
1282
|
+
try {
|
|
1283
|
+
const name = await git.getConfig("user.name");
|
|
1284
|
+
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
1285
|
+
const email = await git.getConfig("user.email");
|
|
1286
|
+
if (!email.value) await git.addConfig("user.email", "auto@gitelectron.local");
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
console.warn("Failed to ensure git identity before pull:", e);
|
|
1289
|
+
}
|
|
1171
1290
|
let targetBranch = branch;
|
|
1172
1291
|
if (!targetBranch) {
|
|
1173
1292
|
const status = await git.status();
|
|
@@ -1185,11 +1304,19 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1185
1304
|
let didStash = false;
|
|
1186
1305
|
try {
|
|
1187
1306
|
const status = await git.status();
|
|
1307
|
+
if (status.conflicted && status.conflicted.length > 0) {
|
|
1308
|
+
throw new Error(
|
|
1309
|
+
`Pulling is not possible because you have unmerged files (${status.conflicted.join(", ")}). Please resolve conflicts or abort the merge.`
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1188
1312
|
if (!status.isClean()) {
|
|
1189
1313
|
await git.raw(["stash", "push", "-u", "-m", "gitquickpush-autostash-before-pull"]);
|
|
1190
1314
|
didStash = true;
|
|
1191
1315
|
}
|
|
1192
1316
|
} catch (err) {
|
|
1317
|
+
if (err.message && err.message.includes("unmerged files")) {
|
|
1318
|
+
throw err;
|
|
1319
|
+
}
|
|
1193
1320
|
console.warn("Stash check before pull warning:", err);
|
|
1194
1321
|
}
|
|
1195
1322
|
try {
|
|
@@ -1207,6 +1334,8 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1207
1334
|
"pull",
|
|
1208
1335
|
"--no-rebase",
|
|
1209
1336
|
"--allow-unrelated-histories",
|
|
1337
|
+
"-X",
|
|
1338
|
+
"ours",
|
|
1210
1339
|
authUrl,
|
|
1211
1340
|
targetBranch
|
|
1212
1341
|
];
|
|
@@ -1366,7 +1495,7 @@ async function getBranches(sessionId) {
|
|
|
1366
1495
|
)
|
|
1367
1496
|
]);
|
|
1368
1497
|
}
|
|
1369
|
-
} catch
|
|
1498
|
+
} catch {
|
|
1370
1499
|
}
|
|
1371
1500
|
let branches = await git.branch(["-a"]);
|
|
1372
1501
|
if (branches.all.length === 0) {
|
|
@@ -1388,10 +1517,14 @@ async function getBranches(sessionId) {
|
|
|
1388
1517
|
}
|
|
1389
1518
|
const allBranches = /* @__PURE__ */ new Set();
|
|
1390
1519
|
branches.all.forEach((b) => {
|
|
1520
|
+
if (b.includes("__master_push_temp_")) return;
|
|
1391
1521
|
if (b.startsWith("remotes/")) {
|
|
1392
1522
|
const parts = b.split("/");
|
|
1393
1523
|
if (parts.length > 2) {
|
|
1394
|
-
|
|
1524
|
+
const branchName = parts.slice(2).join("/");
|
|
1525
|
+
if (!branchName.includes("__master_push_temp_")) {
|
|
1526
|
+
allBranches.add(branchName);
|
|
1527
|
+
}
|
|
1395
1528
|
}
|
|
1396
1529
|
} else {
|
|
1397
1530
|
allBranches.add(b);
|
|
@@ -1478,15 +1611,11 @@ async function pushAllBranches(sessionId, remote) {
|
|
|
1478
1611
|
GIT_TERMINAL_PROMPT: "0",
|
|
1479
1612
|
GIT_ASKPASS: "echo"
|
|
1480
1613
|
});
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
|
|
1485
|
-
}
|
|
1486
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1487
|
-
} catch (err) {
|
|
1488
|
-
throw err;
|
|
1614
|
+
if (remoteObj && remoteObj.refs.push) {
|
|
1615
|
+
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1616
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
|
|
1489
1617
|
}
|
|
1618
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1490
1619
|
}
|
|
1491
1620
|
async function getCommitHistory(sessionId) {
|
|
1492
1621
|
const { git } = getGitSession(sessionId);
|
|
@@ -1504,17 +1633,27 @@ async function getCommitHistory(sessionId) {
|
|
|
1504
1633
|
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1505
1634
|
]);
|
|
1506
1635
|
}
|
|
1507
|
-
} catch
|
|
1636
|
+
} catch {
|
|
1637
|
+
}
|
|
1638
|
+
try {
|
|
1639
|
+
const log = await git.log(["--all", "--stat", "--root", "--cc"]);
|
|
1640
|
+
return JSON.parse(JSON.stringify(log));
|
|
1641
|
+
} catch {
|
|
1642
|
+
try {
|
|
1643
|
+
const log = await git.log(["--all", "--stat", "--root"]);
|
|
1644
|
+
return JSON.parse(JSON.stringify(log));
|
|
1645
|
+
} catch {
|
|
1646
|
+
const log = await git.log(["--all", "--name-status"]);
|
|
1647
|
+
return JSON.parse(JSON.stringify(log));
|
|
1648
|
+
}
|
|
1508
1649
|
}
|
|
1509
|
-
const log = await git.log(["--all", "--name-status"]);
|
|
1510
|
-
return JSON.parse(JSON.stringify(log));
|
|
1511
1650
|
}
|
|
1512
1651
|
async function getDeletedHistory(sessionId) {
|
|
1513
1652
|
const { git } = getGitSession(sessionId);
|
|
1514
1653
|
try {
|
|
1515
1654
|
const log = await git.log(["--all", "--diff-filter=D", "--name-status", "-n", "20"]);
|
|
1516
1655
|
return JSON.parse(JSON.stringify(log));
|
|
1517
|
-
} catch
|
|
1656
|
+
} catch {
|
|
1518
1657
|
return { all: [] };
|
|
1519
1658
|
}
|
|
1520
1659
|
}
|
|
@@ -1608,7 +1747,7 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1608
1747
|
await setupDefaultGitAttributes(workspacePath);
|
|
1609
1748
|
let authUrl = remoteUrl;
|
|
1610
1749
|
if (remoteUrl.includes("github.com")) {
|
|
1611
|
-
|
|
1750
|
+
const cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
|
|
1612
1751
|
if (cleanUrl.startsWith("https://github.com/")) {
|
|
1613
1752
|
authUrl = cleanUrl.replace(
|
|
1614
1753
|
"https://github.com/",
|
|
@@ -1671,7 +1810,7 @@ async function getUniversalDiff(workspacePath, filePath) {
|
|
|
1671
1810
|
try {
|
|
1672
1811
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1673
1812
|
return diff;
|
|
1674
|
-
} catch
|
|
1813
|
+
} catch {
|
|
1675
1814
|
return "";
|
|
1676
1815
|
}
|
|
1677
1816
|
}
|
|
@@ -1686,6 +1825,7 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1686
1825
|
GIT_ASKPASS: "echo"
|
|
1687
1826
|
});
|
|
1688
1827
|
try {
|
|
1828
|
+
await ensureAiWeightsUseLfs(git);
|
|
1689
1829
|
await git.raw([
|
|
1690
1830
|
"-c",
|
|
1691
1831
|
"core.safecrlf=false",
|
|
@@ -1708,7 +1848,7 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1708
1848
|
} catch (err) {
|
|
1709
1849
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1710
1850
|
console.log("Universal push rejected. Auto-pulling with rebase...");
|
|
1711
|
-
await git.pull("origin", branch, { "--rebase":
|
|
1851
|
+
await git.pull("origin", branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1712
1852
|
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
1713
1853
|
return;
|
|
1714
1854
|
}
|
|
@@ -1991,191 +2131,65 @@ async function scanForRepoFolder(repoName, candidateBaseDirs) {
|
|
|
1991
2131
|
async function removeGitLock(sessionId) {
|
|
1992
2132
|
const { repoPath } = getGitSession(sessionId);
|
|
1993
2133
|
if (!repoPath) return false;
|
|
1994
|
-
const lockPath =
|
|
2134
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
1995
2135
|
try {
|
|
1996
|
-
await
|
|
2136
|
+
await fs__namespace.unlink(lockPath);
|
|
1997
2137
|
return true;
|
|
1998
|
-
} catch
|
|
2138
|
+
} catch {
|
|
1999
2139
|
return false;
|
|
2000
2140
|
}
|
|
2001
2141
|
}
|
|
2002
|
-
async function
|
|
2003
|
-
const { git, repoPath } = getGitSession(sessionId);
|
|
2004
|
-
const remotes = await getRemotes(sessionId);
|
|
2005
|
-
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
2006
|
-
if (!remoteObj) throw new Error("No remote repository configured.");
|
|
2007
|
-
const targetRemote = remoteObj.name;
|
|
2008
|
-
const authUrl = remoteObj.refs?.push ? injectAuthToken(sessionId, remoteObj.refs.push) : targetRemote;
|
|
2009
|
-
let originalBranch = branch;
|
|
2142
|
+
async function ensureAiWeightsUseLfs(git) {
|
|
2010
2143
|
try {
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
"
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
"filter.lfs.required=false",
|
|
2027
|
-
"-c",
|
|
2028
|
-
"lfs.allowincompletepush=true",
|
|
2029
|
-
"-c",
|
|
2030
|
-
"http.postBuffer=524288000",
|
|
2031
|
-
"-c",
|
|
2032
|
-
"credential.helper="
|
|
2033
|
-
];
|
|
2034
|
-
function runGit(args, allowFail = false) {
|
|
2035
|
-
return new Promise((resolve, reject) => {
|
|
2036
|
-
const child = child_process.spawn("git", [...lfsOffFlags, ...args], {
|
|
2037
|
-
cwd: repoPath,
|
|
2038
|
-
env: {
|
|
2039
|
-
...process.env,
|
|
2040
|
-
GIT_LFS_SKIP_SMUDGE: "1",
|
|
2041
|
-
GIT_LFS_SKIP_PUSH: "1",
|
|
2042
|
-
GCM_INTERACTIVE: "false",
|
|
2043
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
2044
|
-
GIT_ASKPASS: "echo"
|
|
2045
|
-
}
|
|
2046
|
-
});
|
|
2047
|
-
const stdout = [];
|
|
2048
|
-
const stderr = [];
|
|
2049
|
-
child.stdout?.on("data", (d) => stdout.push(d.toString()));
|
|
2050
|
-
child.stderr?.on("data", (d) => stderr.push(d.toString()));
|
|
2051
|
-
child.on("error", (err) => allowFail ? resolve("") : reject(err));
|
|
2052
|
-
child.on("close", (code) => {
|
|
2053
|
-
if (code === 0 || allowFail) {
|
|
2054
|
-
resolve(stdout.join(""));
|
|
2055
|
-
} else {
|
|
2056
|
-
reject(new Error(stderr.join("") || `git exited with code ${code}`));
|
|
2057
|
-
}
|
|
2058
|
-
});
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
async function processRepo(dir) {
|
|
2062
|
-
let entries;
|
|
2063
|
-
try {
|
|
2064
|
-
entries = await fs__namespace.readdir(dir, { withFileTypes: true });
|
|
2065
|
-
} catch (e) {
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
for (const entry of entries) {
|
|
2069
|
-
if (entry.name === ".git" || entry.name === "node_modules" || entry.name.endsWith(".__lfs_ptr_bak__"))
|
|
2070
|
-
continue;
|
|
2071
|
-
const fullPath = path__namespace.join(dir, entry.name);
|
|
2072
|
-
if (entry.isDirectory()) {
|
|
2073
|
-
await processRepo(fullPath);
|
|
2074
|
-
continue;
|
|
2075
|
-
}
|
|
2076
|
-
if (!entry.isFile()) continue;
|
|
2077
|
-
try {
|
|
2078
|
-
if (entry.name === ".gitattributes") {
|
|
2079
|
-
const content = await fs__namespace.readFile(fullPath, "utf8");
|
|
2080
|
-
if (content.includes("filter=lfs") || content.includes("lfs")) {
|
|
2081
|
-
cleanedAttrsFiles.push({ filePath: fullPath, original: content });
|
|
2082
|
-
const cleaned = content.split("\n").filter((line) => !line.toLowerCase().includes("lfs")).join("\n");
|
|
2083
|
-
await fs__namespace.writeFile(fullPath, cleaned);
|
|
2084
|
-
}
|
|
2085
|
-
continue;
|
|
2086
|
-
}
|
|
2087
|
-
const stat = await fs__namespace.stat(fullPath);
|
|
2088
|
-
if (stat.size < 4096 && stat.size > 20) {
|
|
2089
|
-
const buf = await fs__namespace.readFile(fullPath);
|
|
2090
|
-
const text = buf.toString("utf8");
|
|
2091
|
-
if (text.includes("version https://git-lfs.github.com/spec/v1") && text.includes("oid sha256:")) {
|
|
2092
|
-
const backupPath = fullPath + ".__lfs_ptr_bak__";
|
|
2093
|
-
await fs__namespace.rename(fullPath, backupPath);
|
|
2094
|
-
hiddenPointers.push({ original: fullPath, backup: backupPath });
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
} catch (e) {
|
|
2098
|
-
}
|
|
2144
|
+
const aiExtensions = [
|
|
2145
|
+
"*.pt",
|
|
2146
|
+
"*.pth",
|
|
2147
|
+
"*.ckpt",
|
|
2148
|
+
"*.safetensors",
|
|
2149
|
+
"*.bin",
|
|
2150
|
+
"*.onnx",
|
|
2151
|
+
"*.h5",
|
|
2152
|
+
"*.tflite",
|
|
2153
|
+
"*.pb",
|
|
2154
|
+
"*.gguf"
|
|
2155
|
+
];
|
|
2156
|
+
await git.raw(["lfs", "install"]);
|
|
2157
|
+
for (const ext of aiExtensions) {
|
|
2158
|
+
await git.raw(["lfs", "track", ext]);
|
|
2099
2159
|
}
|
|
2160
|
+
} catch (e) {
|
|
2161
|
+
console.warn("Failed to configure LFS for AI weights:", e);
|
|
2100
2162
|
}
|
|
2163
|
+
}
|
|
2164
|
+
const GITHUB_MODELS_CONFIG = {
|
|
2165
|
+
baseURL: "https://models.inference.ai.azure.com",
|
|
2166
|
+
defaultModel: "gpt-4o-mini",
|
|
2167
|
+
fallbackModel: "meta-llama-3.1-8b-instruct"
|
|
2168
|
+
};
|
|
2169
|
+
const CIPHER_ALGO = "aes-256-cbc";
|
|
2170
|
+
const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
|
|
2171
|
+
const CIPHER_SALT = "salt_gitelectron";
|
|
2172
|
+
const ENCRYPTED_BACKUP_PAYLOAD = "1aa43686251fc27545934564c92ebcd9:acac927d94c7479c0df45b9245f61f47bd4184fca2323e6ce971bc5c74dffaac925caa81788ce81374a05c7072301ee2eac10531be99a850453ff3fd23ea21cd11a8fa95d07d19fe1a8abcdd9a282b06d3e391a621137027c03765f65e977ed432c00e0e9627b2dad77ca708273eff14eac1bbfd01085d4b645a271167bae46edec2a09491b877fba73c3d29dc2e0e2120785347c9189db6bc9892a6018b6dea7ac064b991e9687e2df56e983743985f";
|
|
2173
|
+
function getBackupApiKeys() {
|
|
2101
2174
|
try {
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
await processRepo(repoPath);
|
|
2114
|
-
await runGit(["checkout", "--orphan", tempBranch]);
|
|
2115
|
-
await runGit(["add", "."]);
|
|
2116
|
-
try {
|
|
2117
|
-
const grepResult = await runGit(
|
|
2118
|
-
["grep", "--cached", "-l", "version https://git-lfs.github.com/spec/v1"],
|
|
2119
|
-
true
|
|
2120
|
-
);
|
|
2121
|
-
if (grepResult.trim()) {
|
|
2122
|
-
const lfsFiles = grepResult.trim().split("\n").filter(Boolean);
|
|
2123
|
-
for (const f of lfsFiles) {
|
|
2124
|
-
await runGit(["rm", "--cached", "--force", f.trim()], true);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
} catch (e) {
|
|
2128
|
-
}
|
|
2129
|
-
try {
|
|
2130
|
-
await runGit(["commit", "-m", "Master Override Push", "--no-verify"]);
|
|
2131
|
-
} catch (e) {
|
|
2132
|
-
}
|
|
2133
|
-
await runGit(["push", "--force", "--no-verify", authUrl, `${tempBranch}:${branch}`]);
|
|
2134
|
-
} finally {
|
|
2135
|
-
try {
|
|
2136
|
-
await runGit(["checkout", "-f", originalBranch], true);
|
|
2137
|
-
} catch (e) {
|
|
2138
|
-
try {
|
|
2139
|
-
await runGit(["checkout", "-f", branch], true);
|
|
2140
|
-
} catch (e2) {
|
|
2141
|
-
try {
|
|
2142
|
-
await runGit(["checkout", "-b", branch], true);
|
|
2143
|
-
} catch (e3) {
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
try {
|
|
2148
|
-
const branchList = await runGit(["branch", "--list", "__master_push_temp_*"], true);
|
|
2149
|
-
if (branchList.trim()) {
|
|
2150
|
-
const branches = branchList.trim().split("\n").map((b) => b.trim().replace(/^\*\s*/, "")).filter(Boolean);
|
|
2151
|
-
for (const b of branches) {
|
|
2152
|
-
await runGit(["branch", "-D", b], true);
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
} catch (e) {
|
|
2156
|
-
}
|
|
2157
|
-
try {
|
|
2158
|
-
await runGit(["branch", "-D", tempBranch], true);
|
|
2159
|
-
} catch (e) {
|
|
2160
|
-
}
|
|
2161
|
-
for (const { original, backup } of hiddenPointers) {
|
|
2162
|
-
try {
|
|
2163
|
-
await fs__namespace.rename(backup, original);
|
|
2164
|
-
} catch (e) {
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
for (const { filePath, original } of cleanedAttrsFiles) {
|
|
2168
|
-
try {
|
|
2169
|
-
await fs__namespace.writeFile(filePath, original);
|
|
2170
|
-
} catch (e) {
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
try {
|
|
2174
|
-
await git.raw(["lfs", "install", "--local"]);
|
|
2175
|
-
} catch (e) {
|
|
2176
|
-
}
|
|
2175
|
+
const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
|
|
2176
|
+
if (!ivHex || !dataHex) return [];
|
|
2177
|
+
const key = crypto.scryptSync(CIPHER_SECRET, CIPHER_SALT, 32);
|
|
2178
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
2179
|
+
const decipher = crypto.createDecipheriv(CIPHER_ALGO, key, iv);
|
|
2180
|
+
let decrypted = decipher.update(dataHex, "hex", "utf8");
|
|
2181
|
+
decrypted += decipher.final("utf8");
|
|
2182
|
+
return decrypted.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
console.warn("[AI Setup] Failed to decrypt backup keys in memory:", err);
|
|
2185
|
+
return [];
|
|
2177
2186
|
}
|
|
2178
2187
|
}
|
|
2188
|
+
function isValidGitHubToken(token) {
|
|
2189
|
+
if (!token || typeof token !== "string") return false;
|
|
2190
|
+
const trimmed = token.trim();
|
|
2191
|
+
return trimmed.startsWith("gho_") || trimmed.startsWith("ghp_") || trimmed.startsWith("github_pat_") || trimmed.length >= 30;
|
|
2192
|
+
}
|
|
2179
2193
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
2180
2194
|
The user has encountered a Git error during a push, pull, or merge operation and you are executing as a background process to silently fix it.
|
|
2181
2195
|
You will receive the Git Status and the Raw Error Message.
|
|
@@ -2194,14 +2208,7 @@ Return the response strictly as a JSON object with this shape, and absolutely no
|
|
|
2194
2208
|
"commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
|
|
2195
2209
|
}`;
|
|
2196
2210
|
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2197
|
-
|
|
2198
|
-
if (provider === "gemini") {
|
|
2199
|
-
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
2200
|
-
} else {
|
|
2201
|
-
apiKeyInput = "deepseek-hardcoded-key-if-applicable";
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
2211
|
+
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2205
2212
|
const prompt = `
|
|
2206
2213
|
GIT STATUS:
|
|
2207
2214
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2244,7 +2251,7 @@ ${errorMsg}
|
|
|
2244
2251
|
try {
|
|
2245
2252
|
if (provider === "gemini") {
|
|
2246
2253
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2247
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2254
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2248
2255
|
const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2249
2256
|
responseText = result.response.text();
|
|
2250
2257
|
} else if (provider === "deepseek") {
|
|
@@ -2296,10 +2303,14 @@ ${errorMsg}
|
|
|
2296
2303
|
]
|
|
2297
2304
|
};
|
|
2298
2305
|
}
|
|
2299
|
-
if (lowerError.includes("unmerged files") || lowerError.includes("you have unmerged paths")) {
|
|
2306
|
+
if (lowerError.includes("unmerged files") || lowerError.includes("you have unmerged paths") || lowerError.includes("unresolved conflict")) {
|
|
2300
2307
|
return {
|
|
2301
|
-
explanation: "Unmerged
|
|
2302
|
-
commandsToRun: [
|
|
2308
|
+
explanation: "Unmerged files detected. Resolving conflicts by keeping local changes...",
|
|
2309
|
+
commandsToRun: [
|
|
2310
|
+
"git checkout --ours .",
|
|
2311
|
+
"git add .",
|
|
2312
|
+
'git commit -m "Auto-resolved unmerged conflicts (kept local changes)"'
|
|
2313
|
+
]
|
|
2303
2314
|
};
|
|
2304
2315
|
}
|
|
2305
2316
|
if (lowerError.includes("please tell me who you are")) {
|
|
@@ -2345,10 +2356,7 @@ Guidelines:
|
|
|
2345
2356
|
- If there are no conflicted files (e.g. just a standard rejection that a pull will fix), omit "resolvedCode".
|
|
2346
2357
|
- Return ONLY valid JSON. No markdown code block wrappers.`;
|
|
2347
2358
|
async function analyzePushErrorForAutoSync(provider, apiKeyInput, errorMsg, gitStatus, _repoPath, conflictedFilesContext, githubToken) {
|
|
2348
|
-
|
|
2349
|
-
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
2350
|
-
}
|
|
2351
|
-
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
2359
|
+
const keys = getBackupApiKeys();
|
|
2352
2360
|
let prompt = `
|
|
2353
2361
|
GIT STATUS:
|
|
2354
2362
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2369,14 +2377,14 @@ ${content}
|
|
|
2369
2377
|
}
|
|
2370
2378
|
}
|
|
2371
2379
|
let lastError = null;
|
|
2372
|
-
if (githubToken) {
|
|
2380
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2373
2381
|
try {
|
|
2374
2382
|
const openai = new OpenAI({
|
|
2375
2383
|
apiKey: githubToken,
|
|
2376
|
-
baseURL:
|
|
2384
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2377
2385
|
});
|
|
2378
2386
|
const response = await openai.chat.completions.create({
|
|
2379
|
-
model:
|
|
2387
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2380
2388
|
messages: [
|
|
2381
2389
|
{ role: "system", content: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2382
2390
|
{ role: "user", content: prompt }
|
|
@@ -2398,7 +2406,7 @@ ${content}
|
|
|
2398
2406
|
try {
|
|
2399
2407
|
if (provider === "gemini") {
|
|
2400
2408
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2401
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2409
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2402
2410
|
const result = await model.generateContent([
|
|
2403
2411
|
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2404
2412
|
{ text: prompt }
|
|
@@ -2430,17 +2438,17 @@ If the diff is empty or invalid, return "Auto-sync: update files".`;
|
|
|
2430
2438
|
const prompt = `GIT DIFF:
|
|
2431
2439
|
${truncatedDiff}`;
|
|
2432
2440
|
let keys = "".split(",").map((k) => k.trim()).filter((k) => k);
|
|
2433
|
-
if (!keys.length
|
|
2434
|
-
keys =
|
|
2441
|
+
if (!keys.length) {
|
|
2442
|
+
keys = getBackupApiKeys();
|
|
2435
2443
|
}
|
|
2436
|
-
if (githubToken) {
|
|
2444
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2437
2445
|
try {
|
|
2438
2446
|
const openai = new OpenAI({
|
|
2439
2447
|
apiKey: githubToken,
|
|
2440
|
-
baseURL:
|
|
2448
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2441
2449
|
});
|
|
2442
2450
|
const response = await openai.chat.completions.create({
|
|
2443
|
-
model:
|
|
2451
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2444
2452
|
messages: [
|
|
2445
2453
|
{ role: "system", content: systemPrompt },
|
|
2446
2454
|
{ role: "user", content: prompt }
|
|
@@ -2456,7 +2464,7 @@ ${truncatedDiff}`;
|
|
|
2456
2464
|
try {
|
|
2457
2465
|
if (provider === "gemini") {
|
|
2458
2466
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2459
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2467
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2460
2468
|
const result = await model.generateContent([{ text: systemPrompt }, { text: prompt }]);
|
|
2461
2469
|
return result.response.text().trim();
|
|
2462
2470
|
}
|
|
@@ -2557,7 +2565,11 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2557
2565
|
// ignore Office temp lock files (~$README.md, etc.)
|
|
2558
2566
|
],
|
|
2559
2567
|
persistent: true,
|
|
2560
|
-
ignoreInitial: true
|
|
2568
|
+
ignoreInitial: true,
|
|
2569
|
+
awaitWriteFinish: {
|
|
2570
|
+
stabilityThreshold: 2e3,
|
|
2571
|
+
pollInterval: 100
|
|
2572
|
+
}
|
|
2561
2573
|
});
|
|
2562
2574
|
currentWatcher.on("all", (_event, filePath) => {
|
|
2563
2575
|
if (!isAutoSyncEnabled || isResolving) return;
|
|
@@ -2614,7 +2626,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2614
2626
|
return;
|
|
2615
2627
|
}
|
|
2616
2628
|
const branch = finalStatus.current || "main";
|
|
2617
|
-
onLog("Generating
|
|
2629
|
+
onLog("🤖 Generating commit summary via GitHub Models...");
|
|
2618
2630
|
let commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
|
|
2619
2631
|
try {
|
|
2620
2632
|
const diffStr = await getFileDiff(sessionId, "", true);
|
|
@@ -2632,7 +2644,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2632
2644
|
await commitChanges(sessionId, commitMsg);
|
|
2633
2645
|
onLog(`Pushing to ${branch}...`);
|
|
2634
2646
|
await pushToRemote(sessionId, "origin", branch);
|
|
2635
|
-
onLog(`✅
|
|
2647
|
+
onLog(`✅ Pushed to origin/${branch} [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`);
|
|
2636
2648
|
} catch (error) {
|
|
2637
2649
|
onLog(`❌ Auto-sync failed: ${error.message}`);
|
|
2638
2650
|
if (globalOnErrorApproval && !isResolving) {
|
|
@@ -2664,7 +2676,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2664
2676
|
onLog("Pulling remote changes with rebase...");
|
|
2665
2677
|
const status2 = await getStatus(sessionId);
|
|
2666
2678
|
const currentBranch = status2.current || "main";
|
|
2667
|
-
await git.pull("origin", currentBranch, { "--rebase": "
|
|
2679
|
+
await git.pull("origin", currentBranch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
2668
2680
|
onLog("Pull successful, attempting push again...");
|
|
2669
2681
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2670
2682
|
onLog("✅ Error resolved and push resumed!");
|
|
@@ -2777,6 +2789,27 @@ function cancelAiFix() {
|
|
|
2777
2789
|
isResolving = false;
|
|
2778
2790
|
}
|
|
2779
2791
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
2792
|
+
if (process.platform === "darwin") {
|
|
2793
|
+
const macPaths = [
|
|
2794
|
+
"/opt/homebrew/bin",
|
|
2795
|
+
"/opt/homebrew/sbin",
|
|
2796
|
+
"/usr/local/bin",
|
|
2797
|
+
"/usr/local/sbin",
|
|
2798
|
+
"/usr/bin",
|
|
2799
|
+
"/bin",
|
|
2800
|
+
"/usr/sbin",
|
|
2801
|
+
"/sbin"
|
|
2802
|
+
];
|
|
2803
|
+
const currentPath = process.env.PATH || "";
|
|
2804
|
+
const missingPaths = macPaths.filter((p) => !currentPath.includes(p));
|
|
2805
|
+
if (missingPaths.length > 0) {
|
|
2806
|
+
process.env.PATH = `${missingPaths.join(":")}:${currentPath}`;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (process.platform === "win32") {
|
|
2810
|
+
electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
|
2811
|
+
electron.app.commandLine.appendSwitch("disable-color-correct-rendering");
|
|
2812
|
+
}
|
|
2780
2813
|
function createWindow(initialPath) {
|
|
2781
2814
|
const mainWindow = new electron.BrowserWindow({
|
|
2782
2815
|
width: 1100,
|
|
@@ -2932,15 +2965,15 @@ electron.app.whenReady().then(async () => {
|
|
|
2932
2965
|
}
|
|
2933
2966
|
return await pushToRemote(event.sender.id, remote, branch);
|
|
2934
2967
|
});
|
|
2968
|
+
electron.ipcMain.handle("master-push", async (event, remote, branch) => {
|
|
2969
|
+
return await masterForcePush(event.sender.id, remote, branch);
|
|
2970
|
+
});
|
|
2935
2971
|
electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
|
|
2936
2972
|
return await pullFromRemote(event.sender.id, remote, branch);
|
|
2937
2973
|
});
|
|
2938
2974
|
electron.ipcMain.handle("check-remote-updates", async (event) => {
|
|
2939
2975
|
return await checkRemoteUpdates(event.sender.id);
|
|
2940
2976
|
});
|
|
2941
|
-
electron.ipcMain.handle("master-push", async (event, remote, branch) => {
|
|
2942
|
-
return await masterPush(event.sender.id, remote, branch);
|
|
2943
|
-
});
|
|
2944
2977
|
electron.ipcMain.handle("reset-to-remote", async (event, remote, branch) => {
|
|
2945
2978
|
return await resetToRemote(event.sender.id, remote, branch);
|
|
2946
2979
|
});
|
|
@@ -3131,6 +3164,20 @@ electron.app.whenReady().then(async () => {
|
|
|
3131
3164
|
electron.ipcMain.handle("remove-git-lock", async (event) => {
|
|
3132
3165
|
return await removeGitLock(event.sender.id);
|
|
3133
3166
|
});
|
|
3167
|
+
electron.ipcMain.handle("clear-app-cache", async (event) => {
|
|
3168
|
+
try {
|
|
3169
|
+
if (electron.session.defaultSession) {
|
|
3170
|
+
await electron.session.defaultSession.clearCache();
|
|
3171
|
+
}
|
|
3172
|
+
try {
|
|
3173
|
+
await removeGitLock(event.sender.id);
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
return { success: true };
|
|
3177
|
+
} catch (e) {
|
|
3178
|
+
return { success: false, error: e.message };
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3134
3181
|
electron.ipcMain.handle("check-path-exists", async (_event, targetPath) => {
|
|
3135
3182
|
if (!targetPath) return false;
|
|
3136
3183
|
try {
|
|
@@ -3216,18 +3263,27 @@ electron.app.whenReady().then(async () => {
|
|
|
3216
3263
|
electron.ipcMain.handle("install-update", async () => {
|
|
3217
3264
|
return new Promise((resolve) => {
|
|
3218
3265
|
const { spawn } = require("child_process");
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3266
|
+
if (process.platform === "win32") {
|
|
3267
|
+
const cmdArgs = [
|
|
3268
|
+
"/c",
|
|
3269
|
+
"start",
|
|
3270
|
+
"cmd.exe",
|
|
3271
|
+
"/c",
|
|
3272
|
+
"echo Waiting for Git Electron to close so it can be safely updated... & timeout /t 3 /nobreak > nul & echo Installing Update... & npm i -g gitelectron@latest gitquickpush-cli@latest & echo Update Complete! Starting application... & gitelectron open & exit"
|
|
3273
|
+
];
|
|
3274
|
+
const child = spawn("cmd.exe", cmdArgs, {
|
|
3275
|
+
detached: true,
|
|
3276
|
+
stdio: "ignore"
|
|
3277
|
+
});
|
|
3278
|
+
child.unref();
|
|
3279
|
+
} else {
|
|
3280
|
+
const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
|
|
3281
|
+
const child = spawn("sh", ["-c", shScript], {
|
|
3282
|
+
detached: true,
|
|
3283
|
+
stdio: "ignore"
|
|
3284
|
+
});
|
|
3285
|
+
child.unref();
|
|
3286
|
+
}
|
|
3231
3287
|
resolve(true);
|
|
3232
3288
|
setTimeout(() => {
|
|
3233
3289
|
electron.app.exit(0);
|