gitxelectron 1.4.1 → 1.4.3
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
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
const electron = require("electron");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fs = require("fs/promises");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const child_process = require("child_process");
|
|
5
7
|
const simpleGit = require("simple-git");
|
|
6
8
|
const crypto = require("crypto");
|
|
7
|
-
const
|
|
9
|
+
const fs$1 = require("fs");
|
|
8
10
|
const generativeAi = require("@google/generative-ai");
|
|
9
11
|
const OpenAI = require("openai");
|
|
10
12
|
const chokidar = require("chokidar");
|
|
@@ -27,6 +29,7 @@ function _interopNamespaceDefault(e) {
|
|
|
27
29
|
}
|
|
28
30
|
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
29
31
|
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
|
|
32
|
+
const https__namespace = /* @__PURE__ */ _interopNamespaceDefault(https);
|
|
30
33
|
const GITHUB_CLIENT_ID = "Ov23livWfiaPADGiwSXq";
|
|
31
34
|
let accounts = [];
|
|
32
35
|
const activeSessions = /* @__PURE__ */ new Map();
|
|
@@ -395,6 +398,25 @@ async function createGitHubRepository(sessionId, name, description, isPrivate, a
|
|
|
395
398
|
});
|
|
396
399
|
if (!res.ok) {
|
|
397
400
|
const errorData = await res.json().catch(() => ({}));
|
|
401
|
+
const activeAccount = getActiveAccount(sessionId);
|
|
402
|
+
if (res.status === 422 && activeAccount?.username) {
|
|
403
|
+
try {
|
|
404
|
+
const existingRes = await fetch(
|
|
405
|
+
`https://api.github.com/repos/${activeAccount.username}/${encodeURIComponent(name)}`,
|
|
406
|
+
{
|
|
407
|
+
headers: {
|
|
408
|
+
Authorization: `Bearer ${token}`,
|
|
409
|
+
Accept: "application/vnd.github.v3+json",
|
|
410
|
+
"User-Agent": "gitelectron-App"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
if (existingRes.ok) {
|
|
415
|
+
return await existingRes.json();
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
398
420
|
throw new Error(errorData.message || "Failed to create repository");
|
|
399
421
|
}
|
|
400
422
|
return await res.json();
|
|
@@ -602,16 +624,72 @@ async function declineInvitation(sessionId, invitationId) {
|
|
|
602
624
|
}
|
|
603
625
|
}
|
|
604
626
|
const gitSessions = /* @__PURE__ */ new Map();
|
|
627
|
+
const sessionRepoPaths = /* @__PURE__ */ new Map();
|
|
628
|
+
function recordSessionPath(sessionId, repoPath) {
|
|
629
|
+
if (sessionId && repoPath) {
|
|
630
|
+
sessionRepoPaths.set(sessionId, repoPath);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
let cachedGitBinary = null;
|
|
634
|
+
function getGitBinary() {
|
|
635
|
+
if (cachedGitBinary) return cachedGitBinary;
|
|
636
|
+
try {
|
|
637
|
+
child_process.execSync("git --version", { stdio: "ignore" });
|
|
638
|
+
cachedGitBinary = "git";
|
|
639
|
+
return "git";
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
if (process.platform === "win32") {
|
|
643
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
644
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
645
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
646
|
+
const userProfile = process.env.USERPROFILE || "";
|
|
647
|
+
const candidates = [
|
|
648
|
+
path__namespace.join(programFiles, "Git", "cmd", "git.exe"),
|
|
649
|
+
path__namespace.join(programFiles, "Git", "bin", "git.exe"),
|
|
650
|
+
path__namespace.join(programFiles, "Git", "mingw64", "bin", "git.exe"),
|
|
651
|
+
path__namespace.join(programFilesX86, "Git", "cmd", "git.exe"),
|
|
652
|
+
path__namespace.join(programFilesX86, "Git", "bin", "git.exe"),
|
|
653
|
+
path__namespace.join(localAppData, "Programs", "Git", "cmd", "git.exe"),
|
|
654
|
+
path__namespace.join(localAppData, "Programs", "Git", "bin", "git.exe"),
|
|
655
|
+
path__namespace.join(userProfile, "scoop", "shims", "git.exe")
|
|
656
|
+
];
|
|
657
|
+
for (const cand of candidates) {
|
|
658
|
+
try {
|
|
659
|
+
if (fs$1.existsSync(cand)) {
|
|
660
|
+
cachedGitBinary = cand;
|
|
661
|
+
return cand;
|
|
662
|
+
}
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} else if (process.platform === "darwin") {
|
|
667
|
+
const candidates = ["/opt/homebrew/bin/git", "/usr/local/bin/git", "/usr/bin/git", "/bin/git"];
|
|
668
|
+
for (const cand of candidates) {
|
|
669
|
+
try {
|
|
670
|
+
if (fs$1.existsSync(cand)) {
|
|
671
|
+
cachedGitBinary = cand;
|
|
672
|
+
return cand;
|
|
673
|
+
}
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
cachedGitBinary = "git";
|
|
679
|
+
return "git";
|
|
680
|
+
}
|
|
605
681
|
function initGit(sessionId, baseDir) {
|
|
682
|
+
recordSessionPath(sessionId, baseDir);
|
|
606
683
|
const options = {
|
|
607
684
|
baseDir,
|
|
608
|
-
binary:
|
|
685
|
+
binary: getGitBinary(),
|
|
609
686
|
maxConcurrentProcesses: 6,
|
|
610
687
|
unsafe: {
|
|
611
688
|
allowUnsafeEditor: true,
|
|
612
689
|
allowUnsafeAskPass: true,
|
|
613
690
|
allowUnsafeCredentialHelper: true,
|
|
614
|
-
allowUnsafeFilter: true
|
|
691
|
+
allowUnsafeFilter: true,
|
|
692
|
+
allowUnsafeCustomBinary: true
|
|
615
693
|
},
|
|
616
694
|
progress: ({ method, stage, progress }) => {
|
|
617
695
|
try {
|
|
@@ -628,6 +706,8 @@ function initGit(sessionId, baseDir) {
|
|
|
628
706
|
GIT_ASKPASS: "echo"
|
|
629
707
|
});
|
|
630
708
|
gitSessions.set(sessionId, { git, repoPath: baseDir });
|
|
709
|
+
checkAndHealZombieGitState(baseDir).catch(() => {
|
|
710
|
+
});
|
|
631
711
|
(async () => {
|
|
632
712
|
try {
|
|
633
713
|
const name = await git.getConfig("user.name");
|
|
@@ -645,13 +725,14 @@ function initGit(sessionId, baseDir) {
|
|
|
645
725
|
}
|
|
646
726
|
function createSafeGit(baseDir, sessionId) {
|
|
647
727
|
const options = {
|
|
648
|
-
binary:
|
|
728
|
+
binary: getGitBinary(),
|
|
649
729
|
maxConcurrentProcesses: 6,
|
|
650
730
|
unsafe: {
|
|
651
731
|
allowUnsafeEditor: true,
|
|
652
732
|
allowUnsafeAskPass: true,
|
|
653
733
|
allowUnsafeCredentialHelper: true,
|
|
654
|
-
allowUnsafeFilter: true
|
|
734
|
+
allowUnsafeFilter: true,
|
|
735
|
+
allowUnsafeCustomBinary: true
|
|
655
736
|
},
|
|
656
737
|
progress: ({ method, stage, progress }) => {
|
|
657
738
|
if (!sessionId) return;
|
|
@@ -674,9 +755,31 @@ function createSafeGit(baseDir, sessionId) {
|
|
|
674
755
|
}
|
|
675
756
|
function cleanupGitSession(sessionId) {
|
|
676
757
|
gitSessions.delete(sessionId);
|
|
758
|
+
sessionRepoPaths.delete(sessionId);
|
|
677
759
|
}
|
|
678
760
|
function getGitSession(sessionId) {
|
|
679
|
-
|
|
761
|
+
let session = gitSessions.get(sessionId);
|
|
762
|
+
if (!session) {
|
|
763
|
+
const knownPath = sessionRepoPaths.get(sessionId);
|
|
764
|
+
if (knownPath) {
|
|
765
|
+
try {
|
|
766
|
+
const gitDir = path__namespace.join(knownPath, ".git");
|
|
767
|
+
if (fs$1.existsSync(gitDir)) {
|
|
768
|
+
initGit(sessionId, knownPath);
|
|
769
|
+
session = gitSessions.get(sessionId);
|
|
770
|
+
}
|
|
771
|
+
} catch (e) {
|
|
772
|
+
console.warn("Auto-recovery of git session failed:", e);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!session && gitSessions.size === 1) {
|
|
777
|
+
const singleSession = gitSessions.values().next().value;
|
|
778
|
+
if (singleSession) {
|
|
779
|
+
gitSessions.set(sessionId, singleSession);
|
|
780
|
+
session = singleSession;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
680
783
|
if (!session) throw new Error("Git not initialized for this window");
|
|
681
784
|
return session;
|
|
682
785
|
}
|
|
@@ -732,129 +835,220 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
732
835
|
} catch {
|
|
733
836
|
}
|
|
734
837
|
}
|
|
735
|
-
async function
|
|
736
|
-
|
|
838
|
+
async function findGitRoot(dir) {
|
|
839
|
+
if (!dir) return null;
|
|
840
|
+
const cleanDir = path__namespace.normalize(dir.trim());
|
|
737
841
|
try {
|
|
738
|
-
|
|
739
|
-
|
|
842
|
+
if (fs$1.existsSync(path__namespace.join(cleanDir, ".git"))) {
|
|
843
|
+
try {
|
|
844
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", cleanDir]);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
return cleanDir;
|
|
848
|
+
}
|
|
740
849
|
} catch {
|
|
741
|
-
hasGitFolder = false;
|
|
742
850
|
}
|
|
743
851
|
try {
|
|
744
|
-
const tempGit =
|
|
745
|
-
...process.env,
|
|
746
|
-
GCM_INTERACTIVE: "false",
|
|
747
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
748
|
-
GIT_ASKPASS: "echo"
|
|
749
|
-
});
|
|
852
|
+
const tempGit = createSafeGit(cleanDir);
|
|
750
853
|
const isRepo = await tempGit.checkIsRepo();
|
|
751
|
-
if (isRepo)
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
854
|
+
if (isRepo) {
|
|
855
|
+
const topLevel = (await tempGit.revparse(["--show-toplevel"])).trim();
|
|
856
|
+
if (topLevel) {
|
|
857
|
+
const normTop = path__namespace.normalize(topLevel);
|
|
858
|
+
try {
|
|
859
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", normTop]);
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
return normTop;
|
|
863
|
+
}
|
|
864
|
+
return cleanDir;
|
|
865
|
+
}
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
let current = path__namespace.dirname(cleanDir);
|
|
870
|
+
while (current && current.length > 3) {
|
|
871
|
+
if (fs$1.existsSync(path__namespace.join(current, ".git"))) {
|
|
872
|
+
try {
|
|
873
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", current]);
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
return current;
|
|
877
|
+
}
|
|
878
|
+
const parent = path__namespace.dirname(current);
|
|
879
|
+
if (!parent || parent === current) break;
|
|
880
|
+
current = parent;
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const entries = await fs__namespace.readdir(cleanDir, { withFileTypes: true });
|
|
886
|
+
for (const entry of entries) {
|
|
887
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
888
|
+
const subPath = path__namespace.join(cleanDir, entry.name);
|
|
889
|
+
if (fs$1.existsSync(path__namespace.join(subPath, ".git"))) {
|
|
890
|
+
try {
|
|
891
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", subPath]);
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
return subPath;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
755
897
|
}
|
|
756
|
-
return false;
|
|
757
898
|
} catch {
|
|
758
|
-
|
|
899
|
+
}
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
async function isGitRepo(dir) {
|
|
903
|
+
if (!dir) return false;
|
|
904
|
+
const cleanDir = path__namespace.normalize(dir.trim());
|
|
905
|
+
try {
|
|
906
|
+
if (fs$1.existsSync(path__namespace.join(cleanDir, ".git"))) {
|
|
759
907
|
try {
|
|
760
|
-
await
|
|
761
|
-
const tempGit = simpleGit.simpleGit(dir).env({
|
|
762
|
-
...process.env,
|
|
763
|
-
GCM_INTERACTIVE: "false",
|
|
764
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
765
|
-
GIT_ASKPASS: "echo"
|
|
766
|
-
});
|
|
767
|
-
return await tempGit.checkIsRepo();
|
|
908
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", cleanDir]);
|
|
768
909
|
} catch {
|
|
769
|
-
return true;
|
|
770
910
|
}
|
|
911
|
+
return true;
|
|
771
912
|
}
|
|
772
|
-
|
|
913
|
+
} catch {
|
|
773
914
|
}
|
|
915
|
+
const root = await findGitRoot(cleanDir);
|
|
916
|
+
return root !== null;
|
|
774
917
|
}
|
|
775
918
|
async function initializeRepo(sessionId, dir) {
|
|
776
919
|
try {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
unsafe: {
|
|
780
|
-
allowUnsafeEditor: true,
|
|
781
|
-
allowUnsafeAskPass: true,
|
|
782
|
-
allowUnsafeCredentialHelper: true
|
|
783
|
-
}
|
|
920
|
+
recordSessionPath(sessionId, dir);
|
|
921
|
+
await fs__namespace.mkdir(dir, { recursive: true }).catch(() => {
|
|
784
922
|
});
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
await tempGit.raw([
|
|
789
|
-
"-c",
|
|
790
|
-
"user.name=gitelectron",
|
|
791
|
-
"-c",
|
|
792
|
-
"user.email=noreply@gitelectron.local",
|
|
793
|
-
"commit",
|
|
794
|
-
"--allow-empty",
|
|
795
|
-
"-m",
|
|
796
|
-
"Initial repository setup"
|
|
797
|
-
]);
|
|
923
|
+
try {
|
|
924
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", dir]);
|
|
925
|
+
} catch {
|
|
798
926
|
}
|
|
799
|
-
|
|
927
|
+
const tempGit = createSafeGit(dir, sessionId);
|
|
928
|
+
await tempGit.init();
|
|
800
929
|
initGit(sessionId, dir);
|
|
930
|
+
try {
|
|
931
|
+
const branches = await tempGit.branchLocal();
|
|
932
|
+
if (branches.all.length === 0) {
|
|
933
|
+
await tempGit.raw([
|
|
934
|
+
"-c",
|
|
935
|
+
"user.name=gitelectron",
|
|
936
|
+
"-c",
|
|
937
|
+
"user.email=noreply@gitelectron.local",
|
|
938
|
+
"-c",
|
|
939
|
+
"commit.gpgsign=false",
|
|
940
|
+
"commit",
|
|
941
|
+
"--allow-empty",
|
|
942
|
+
"--no-verify",
|
|
943
|
+
"--no-gpg-sign",
|
|
944
|
+
"-m",
|
|
945
|
+
"Initial repository setup"
|
|
946
|
+
]);
|
|
947
|
+
}
|
|
948
|
+
} catch (commitErr) {
|
|
949
|
+
console.warn("Initial commit in new repo skipped or failed:", commitErr);
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
await setupDefaultGitAttributes(dir);
|
|
953
|
+
} catch (attrErr) {
|
|
954
|
+
console.warn("Default gitattributes setup failed:", attrErr);
|
|
955
|
+
}
|
|
801
956
|
return true;
|
|
802
957
|
} catch (e) {
|
|
803
958
|
console.error("Failed to init repo", e);
|
|
804
|
-
|
|
959
|
+
throw new Error(`Failed to initialize Git repository in ${dir}: ${e?.message || e}`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
async function checkAndHealZombieGitState(repoPath) {
|
|
963
|
+
if (!repoPath) return;
|
|
964
|
+
try {
|
|
965
|
+
const gitDir = path__namespace.join(repoPath, ".git");
|
|
966
|
+
if (!fs$1.existsSync(gitDir)) return;
|
|
967
|
+
const rebaseMerge = path__namespace.join(gitDir, "rebase-merge");
|
|
968
|
+
const rebaseApply = path__namespace.join(gitDir, "rebase-apply");
|
|
969
|
+
const rebaseHead = path__namespace.join(gitDir, "REBASE_HEAD");
|
|
970
|
+
const indexLock = path__namespace.join(gitDir, "index.lock");
|
|
971
|
+
if (fs$1.existsSync(rebaseMerge) || fs$1.existsSync(rebaseApply) || fs$1.existsSync(rebaseHead)) {
|
|
972
|
+
console.warn(
|
|
973
|
+
"Zombie or interrupted rebase detected at:",
|
|
974
|
+
repoPath,
|
|
975
|
+
"- attempting git rebase --abort"
|
|
976
|
+
);
|
|
977
|
+
try {
|
|
978
|
+
const git = createSafeGit(repoPath);
|
|
979
|
+
await git.raw(["rebase", "--abort"]);
|
|
980
|
+
} catch (abortErr) {
|
|
981
|
+
console.warn("git rebase --abort notice:", abortErr);
|
|
982
|
+
}
|
|
983
|
+
if (fs$1.existsSync(rebaseMerge)) {
|
|
984
|
+
await fs__namespace.rm(rebaseMerge, { recursive: true, force: true }).catch(() => {
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
if (fs$1.existsSync(rebaseApply)) {
|
|
988
|
+
await fs__namespace.rm(rebaseApply, { recursive: true, force: true }).catch(() => {
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
if (fs$1.existsSync(rebaseHead)) {
|
|
992
|
+
await fs__namespace.rm(rebaseHead, { force: true }).catch(() => {
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (fs$1.existsSync(indexLock)) {
|
|
997
|
+
try {
|
|
998
|
+
const stats = await fs__namespace.stat(indexLock);
|
|
999
|
+
if (Date.now() - stats.mtimeMs > 5e3) {
|
|
1000
|
+
console.warn("Stale index.lock detected at:", repoPath, "- removing lock");
|
|
1001
|
+
await fs__namespace.unlink(indexLock).catch(() => {
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
console.warn("checkAndHealZombieGitState error:", e);
|
|
805
1009
|
}
|
|
806
1010
|
}
|
|
807
1011
|
async function getStatus(sessionId) {
|
|
808
1012
|
const { git, repoPath } = getGitSession(sessionId);
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1013
|
+
if (repoPath) {
|
|
1014
|
+
await checkAndHealZombieGitState(repoPath);
|
|
1015
|
+
}
|
|
1016
|
+
const status = await git.status();
|
|
1017
|
+
const result = JSON.parse(JSON.stringify(status));
|
|
1018
|
+
result.isMassive = false;
|
|
1019
|
+
if (!result.staged) result.staged = [];
|
|
1020
|
+
if (!result.tracking && result.current) {
|
|
1021
|
+
try {
|
|
1022
|
+
const remotes = await git.getRemotes();
|
|
1023
|
+
if (remotes.length > 0) {
|
|
1024
|
+
const remoteName = remotes[0].name || "origin";
|
|
1025
|
+
const trackingRef = `refs/remotes/${remoteName}/${result.current}`;
|
|
1026
|
+
const refExists = await git.raw(["rev-parse", "--verify", trackingRef]).catch(() => null);
|
|
1027
|
+
if (refExists) {
|
|
1028
|
+
await git.raw(["branch", `--set-upstream-to=${remoteName}/${result.current}`, result.current]).catch(() => {
|
|
1029
|
+
});
|
|
1030
|
+
result.tracking = `${remoteName}/${result.current}`;
|
|
1031
|
+
try {
|
|
1032
|
+
const revCount = await git.raw([
|
|
1033
|
+
"rev-list",
|
|
1034
|
+
"--left-right",
|
|
1035
|
+
"--count",
|
|
1036
|
+
`${result.current}...${remoteName}/${result.current}`
|
|
1037
|
+
]);
|
|
1038
|
+
const parts = revCount.trim().split(/\s+/);
|
|
1039
|
+
if (parts.length === 2) {
|
|
1040
|
+
result.ahead = parseInt(parts[0], 10) || 0;
|
|
1041
|
+
result.behind = parseInt(parts[1], 10) || 0;
|
|
1042
|
+
}
|
|
1043
|
+
} catch {
|
|
821
1044
|
}
|
|
822
1045
|
}
|
|
823
1046
|
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
created: [],
|
|
830
|
-
deleted: [],
|
|
831
|
-
modified: [],
|
|
832
|
-
conflicted: [],
|
|
833
|
-
renamed: [],
|
|
834
|
-
staged: [],
|
|
835
|
-
files: [],
|
|
836
|
-
current: "main",
|
|
837
|
-
ahead: 0,
|
|
838
|
-
behind: 0,
|
|
839
|
-
tracking: null,
|
|
840
|
-
isMassive: true,
|
|
841
|
-
totalChanges: count
|
|
842
|
-
});
|
|
843
|
-
} else {
|
|
844
|
-
try {
|
|
845
|
-
const status = await git.status();
|
|
846
|
-
const result = JSON.parse(JSON.stringify(status));
|
|
847
|
-
result.isMassive = false;
|
|
848
|
-
result.totalChanges = count;
|
|
849
|
-
if (!result.staged) result.staged = [];
|
|
850
|
-
resolve(result);
|
|
851
|
-
} catch (e) {
|
|
852
|
-
reject(e);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
|
-
child.on("error", reject);
|
|
857
|
-
});
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
result.totalChanges = result.files?.length || 0;
|
|
1051
|
+
return result;
|
|
858
1052
|
}
|
|
859
1053
|
async function stageFiles(sessionId, files) {
|
|
860
1054
|
const { git, repoPath } = getGitSession(sessionId);
|
|
@@ -969,7 +1163,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
969
1163
|
await ensureAiWeightsUseLfs(git);
|
|
970
1164
|
if (onProgress) onProgress("Starting rapid file count to prepare massive chunking...");
|
|
971
1165
|
const totalFiles = await new Promise((resolve) => {
|
|
972
|
-
const child2 = child_process.spawn(
|
|
1166
|
+
const child2 = child_process.spawn(getGitBinary(), ["status", "--porcelain"], { cwd: repoPath });
|
|
973
1167
|
let count = 0;
|
|
974
1168
|
child2.stdout.on("data", (chunk) => {
|
|
975
1169
|
for (let i = 0; i < chunk.length; ++i) {
|
|
@@ -994,7 +1188,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
994
1188
|
if (remoteObj && remoteObj.refs.push) {
|
|
995
1189
|
authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
996
1190
|
}
|
|
997
|
-
const child = child_process.spawn(
|
|
1191
|
+
const child = child_process.spawn(getGitBinary(), ["status", "--porcelain", "-z"], { cwd: repoPath });
|
|
998
1192
|
let buffer = "";
|
|
999
1193
|
let currentChunk = [];
|
|
1000
1194
|
let chunkCount = 0;
|
|
@@ -1118,6 +1312,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1118
1312
|
"-c",
|
|
1119
1313
|
"lfs.concurrenttransfers=8",
|
|
1120
1314
|
"push",
|
|
1315
|
+
"--set-upstream",
|
|
1121
1316
|
"--progress",
|
|
1122
1317
|
"--no-verify"
|
|
1123
1318
|
];
|
|
@@ -1141,15 +1336,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1141
1336
|
async function pushToRemote(sessionId, remote, branch) {
|
|
1142
1337
|
const remotes = await getRemotes(sessionId);
|
|
1143
1338
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1144
|
-
const tempGit =
|
|
1145
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1146
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1147
|
-
}).env({
|
|
1148
|
-
...process.env,
|
|
1149
|
-
GCM_INTERACTIVE: "false",
|
|
1150
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1151
|
-
GIT_ASKPASS: "echo"
|
|
1152
|
-
});
|
|
1339
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1153
1340
|
const doPush = async () => {
|
|
1154
1341
|
const pushArgs = [
|
|
1155
1342
|
"-c",
|
|
@@ -1165,6 +1352,7 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1165
1352
|
"-c",
|
|
1166
1353
|
"lfs.concurrenttransfers=8",
|
|
1167
1354
|
"push",
|
|
1355
|
+
"--set-upstream",
|
|
1168
1356
|
"--progress",
|
|
1169
1357
|
"--no-verify"
|
|
1170
1358
|
];
|
|
@@ -1242,17 +1430,7 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1242
1430
|
console.warn("Commit note:", e.message);
|
|
1243
1431
|
}
|
|
1244
1432
|
}
|
|
1245
|
-
const tempGit =
|
|
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
|
-
});
|
|
1433
|
+
const tempGit = createSafeGit(repoPath, sessionId);
|
|
1256
1434
|
const pushArgs = [
|
|
1257
1435
|
"-c",
|
|
1258
1436
|
"credential.helper=",
|
|
@@ -1261,6 +1439,7 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1261
1439
|
"-c",
|
|
1262
1440
|
"lfs.allowincompletepush=true",
|
|
1263
1441
|
"push",
|
|
1442
|
+
"--set-upstream",
|
|
1264
1443
|
"--progress",
|
|
1265
1444
|
"--no-verify",
|
|
1266
1445
|
"-f"
|
|
@@ -1340,7 +1519,7 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1340
1519
|
targetBranch
|
|
1341
1520
|
];
|
|
1342
1521
|
const pullOutput = await new Promise((resolve, reject) => {
|
|
1343
|
-
const child = child_process.spawn(
|
|
1522
|
+
const child = child_process.spawn(getGitBinary(), pullArgs, {
|
|
1344
1523
|
cwd: repoPath,
|
|
1345
1524
|
env: {
|
|
1346
1525
|
...process.env,
|
|
@@ -1415,7 +1594,7 @@ async function checkRemoteUpdates(sessionId) {
|
|
|
1415
1594
|
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
|
|
1416
1595
|
await new Promise((resolve) => {
|
|
1417
1596
|
const child = child_process.spawn(
|
|
1418
|
-
|
|
1597
|
+
getGitBinary(),
|
|
1419
1598
|
[
|
|
1420
1599
|
"-c",
|
|
1421
1600
|
"credential.helper=",
|
|
@@ -1473,19 +1652,7 @@ async function getBranches(sessionId) {
|
|
|
1473
1652
|
const remotes = await git.getRemotes(true);
|
|
1474
1653
|
if (remotes.length > 0) {
|
|
1475
1654
|
const remoteObj = remotes[0];
|
|
1476
|
-
const tempGit =
|
|
1477
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1478
|
-
unsafe: {
|
|
1479
|
-
allowUnsafeEditor: true,
|
|
1480
|
-
allowUnsafeAskPass: true,
|
|
1481
|
-
allowUnsafeCredentialHelper: true
|
|
1482
|
-
}
|
|
1483
|
-
}).env({
|
|
1484
|
-
...process.env,
|
|
1485
|
-
GCM_INTERACTIVE: "false",
|
|
1486
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1487
|
-
GIT_ASKPASS: "echo"
|
|
1488
|
-
});
|
|
1655
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1489
1656
|
const FETCH_TIMEOUT_MS = 15e3;
|
|
1490
1657
|
const fetchPromise = remoteObj.refs.fetch ? tempGit.fetch(injectAuthToken(sessionId, remoteObj.refs.fetch), { "--prune": null }) : tempGit.fetch(remoteObj.name, { "--prune": null });
|
|
1491
1658
|
await Promise.race([
|
|
@@ -1569,19 +1736,7 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1569
1736
|
const remoteName = remotes[0].name;
|
|
1570
1737
|
await git.addConfig(`branch.${branch}.remote`, remoteName);
|
|
1571
1738
|
await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
|
|
1572
|
-
const tempGit =
|
|
1573
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1574
|
-
unsafe: {
|
|
1575
|
-
allowUnsafeEditor: true,
|
|
1576
|
-
allowUnsafeAskPass: true,
|
|
1577
|
-
allowUnsafeCredentialHelper: true
|
|
1578
|
-
}
|
|
1579
|
-
}).env({
|
|
1580
|
-
...process.env,
|
|
1581
|
-
GCM_INTERACTIVE: "false",
|
|
1582
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1583
|
-
GIT_ASKPASS: "echo"
|
|
1584
|
-
});
|
|
1739
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1585
1740
|
const remoteObj = remotes[0];
|
|
1586
1741
|
if (remoteObj && remoteObj.refs.push) {
|
|
1587
1742
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
@@ -1597,25 +1752,31 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1597
1752
|
}
|
|
1598
1753
|
async function addRemote(sessionId, name, url) {
|
|
1599
1754
|
const { git } = getGitSession(sessionId);
|
|
1755
|
+
try {
|
|
1756
|
+
const remotes = await git.getRemotes(false);
|
|
1757
|
+
if (remotes.some((r) => r.name === name)) {
|
|
1758
|
+
return await git.remote(["set-url", name, url]);
|
|
1759
|
+
}
|
|
1760
|
+
} catch {
|
|
1761
|
+
}
|
|
1600
1762
|
return await git.addRemote(name, url);
|
|
1601
1763
|
}
|
|
1602
1764
|
async function pushAllBranches(sessionId, remote) {
|
|
1603
1765
|
const remotes = await getRemotes(sessionId);
|
|
1604
1766
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1605
|
-
const tempGit =
|
|
1606
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1607
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1608
|
-
}).env({
|
|
1609
|
-
...process.env,
|
|
1610
|
-
GCM_INTERACTIVE: "false",
|
|
1611
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1612
|
-
GIT_ASKPASS: "echo"
|
|
1613
|
-
});
|
|
1767
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1614
1768
|
if (remoteObj && remoteObj.refs.push) {
|
|
1615
1769
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1616
|
-
return await tempGit.raw([
|
|
1770
|
+
return await tempGit.raw([
|
|
1771
|
+
"-c",
|
|
1772
|
+
"credential.helper=",
|
|
1773
|
+
"push",
|
|
1774
|
+
"--set-upstream",
|
|
1775
|
+
authUrl,
|
|
1776
|
+
"--all"
|
|
1777
|
+
]);
|
|
1617
1778
|
}
|
|
1618
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1779
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", "--set-upstream", remote, "--all"]);
|
|
1619
1780
|
}
|
|
1620
1781
|
async function getCommitHistory(sessionId) {
|
|
1621
1782
|
const { git } = getGitSession(sessionId);
|
|
@@ -1685,7 +1846,7 @@ async function setGitConfig(sessionId, key, value, global = false) {
|
|
|
1685
1846
|
gitInstance = getGitSession(sessionId).git;
|
|
1686
1847
|
} catch (e) {
|
|
1687
1848
|
if (global) {
|
|
1688
|
-
gitInstance =
|
|
1849
|
+
gitInstance = createSafeGit();
|
|
1689
1850
|
} else {
|
|
1690
1851
|
throw e;
|
|
1691
1852
|
}
|
|
@@ -1739,10 +1900,11 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1739
1900
|
const tempId = crypto.randomUUID();
|
|
1740
1901
|
const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
|
|
1741
1902
|
await fs__namespace.mkdir(workspacePath, { recursive: true });
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1903
|
+
try {
|
|
1904
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", workspacePath]);
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
const git = createSafeGit(workspacePath, sessionId);
|
|
1746
1908
|
await git.init();
|
|
1747
1909
|
await setupDefaultGitAttributes(workspacePath);
|
|
1748
1910
|
let authUrl = remoteUrl;
|
|
@@ -1765,9 +1927,13 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1765
1927
|
await git.addConfig("user.name", "gitelectron");
|
|
1766
1928
|
await git.addConfig("user.email", "noreply@gitelectron.local");
|
|
1767
1929
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1930
|
+
try {
|
|
1931
|
+
await git.raw(["sparse-checkout", "init", "--cone"]);
|
|
1932
|
+
const sparsePath = destinationPath === "/" || destinationPath === "" ? "" : destinationPath;
|
|
1933
|
+
await git.raw(["sparse-checkout", "set", sparsePath]);
|
|
1934
|
+
} catch (sparseErr) {
|
|
1935
|
+
console.warn("Sparse checkout setup notice:", sparseErr);
|
|
1936
|
+
}
|
|
1771
1937
|
const branchToFetch = baseBranch || branch;
|
|
1772
1938
|
try {
|
|
1773
1939
|
await git.fetch(["--depth", "1", "origin", branchToFetch]);
|
|
@@ -1795,7 +1961,7 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1795
1961
|
await fs__namespace.copyFile(source, path__namespace.join(destDirFull, path__namespace.basename(source)));
|
|
1796
1962
|
}
|
|
1797
1963
|
}
|
|
1798
|
-
const git =
|
|
1964
|
+
const git = createSafeGit(workspacePath);
|
|
1799
1965
|
const status = await git.status();
|
|
1800
1966
|
return {
|
|
1801
1967
|
modified: status.modified,
|
|
@@ -1803,10 +1969,7 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1803
1969
|
};
|
|
1804
1970
|
}
|
|
1805
1971
|
async function getUniversalDiff(workspacePath, filePath) {
|
|
1806
|
-
const git =
|
|
1807
|
-
baseDir: workspacePath,
|
|
1808
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1809
|
-
});
|
|
1972
|
+
const git = createSafeGit(workspacePath);
|
|
1810
1973
|
try {
|
|
1811
1974
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1812
1975
|
return diff;
|
|
@@ -1815,15 +1978,7 @@ async function getUniversalDiff(workspacePath, filePath) {
|
|
|
1815
1978
|
}
|
|
1816
1979
|
}
|
|
1817
1980
|
async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
1818
|
-
const git =
|
|
1819
|
-
baseDir: workspacePath,
|
|
1820
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1821
|
-
}).env({
|
|
1822
|
-
...process.env,
|
|
1823
|
-
GCM_INTERACTIVE: "false",
|
|
1824
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1825
|
-
GIT_ASKPASS: "echo"
|
|
1826
|
-
});
|
|
1981
|
+
const git = createSafeGit(workspacePath);
|
|
1827
1982
|
try {
|
|
1828
1983
|
await ensureAiWeightsUseLfs(git);
|
|
1829
1984
|
await git.raw([
|
|
@@ -1844,12 +1999,12 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1844
1999
|
}
|
|
1845
2000
|
await git.commit(message);
|
|
1846
2001
|
try {
|
|
1847
|
-
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
2002
|
+
await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
|
|
1848
2003
|
} catch (err) {
|
|
1849
2004
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1850
2005
|
console.log("Universal push rejected. Auto-pulling with rebase...");
|
|
1851
2006
|
await git.pull("origin", branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1852
|
-
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
2007
|
+
await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
|
|
1853
2008
|
return;
|
|
1854
2009
|
}
|
|
1855
2010
|
throw err;
|
|
@@ -1910,7 +2065,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1910
2065
|
await existingGit.raw(["-c", "credential.helper=", "fetch", "--progress", "origin"]);
|
|
1911
2066
|
await new Promise((resolve) => {
|
|
1912
2067
|
const child = child_process.spawn(
|
|
1913
|
-
|
|
2068
|
+
getGitBinary(),
|
|
1914
2069
|
[
|
|
1915
2070
|
"-c",
|
|
1916
2071
|
"core.longpaths=true",
|
|
@@ -1968,7 +2123,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1968
2123
|
cwd = path__namespace.dirname(destPath);
|
|
1969
2124
|
}
|
|
1970
2125
|
await new Promise((resolve, reject) => {
|
|
1971
|
-
const child = child_process.spawn(
|
|
2126
|
+
const child = child_process.spawn(getGitBinary(), cloneArgs, {
|
|
1972
2127
|
cwd,
|
|
1973
2128
|
env: {
|
|
1974
2129
|
...process.env,
|
|
@@ -2061,13 +2216,14 @@ async function removeGitMetadata(_sessionId, repoPath) {
|
|
|
2061
2216
|
}
|
|
2062
2217
|
async function getDirectoryGitInfo(dir) {
|
|
2063
2218
|
try {
|
|
2064
|
-
const
|
|
2219
|
+
const rootPath = await findGitRoot(dir) || dir;
|
|
2220
|
+
const isRepo = await isGitRepo(rootPath);
|
|
2065
2221
|
if (!isRepo) return { isRepo: false, remotes: [] };
|
|
2066
|
-
const tempGit = createSafeGit(
|
|
2067
|
-
let
|
|
2222
|
+
const tempGit = createSafeGit(rootPath);
|
|
2223
|
+
let detectedRoot = void 0;
|
|
2068
2224
|
try {
|
|
2069
|
-
|
|
2070
|
-
if (
|
|
2225
|
+
detectedRoot = (await tempGit.revparse(["--show-toplevel"])).trim();
|
|
2226
|
+
if (detectedRoot) detectedRoot = path__namespace.normalize(detectedRoot);
|
|
2071
2227
|
} catch {
|
|
2072
2228
|
}
|
|
2073
2229
|
const remotes = await tempGit.getRemotes(true);
|
|
@@ -2083,7 +2239,7 @@ async function getDirectoryGitInfo(dir) {
|
|
|
2083
2239
|
}
|
|
2084
2240
|
return {
|
|
2085
2241
|
isRepo: true,
|
|
2086
|
-
rootPath:
|
|
2242
|
+
rootPath: detectedRoot || rootPath,
|
|
2087
2243
|
remotes: JSON.parse(JSON.stringify(remotes)),
|
|
2088
2244
|
repoName,
|
|
2089
2245
|
remoteUrl
|
|
@@ -2169,7 +2325,7 @@ const GITHUB_MODELS_CONFIG = {
|
|
|
2169
2325
|
const CIPHER_ALGO = "aes-256-cbc";
|
|
2170
2326
|
const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
|
|
2171
2327
|
const CIPHER_SALT = "salt_gitelectron";
|
|
2172
|
-
const ENCRYPTED_BACKUP_PAYLOAD = "
|
|
2328
|
+
const ENCRYPTED_BACKUP_PAYLOAD = "19957ea42600d64c44b82d056696e39a:ff16f32b11fb7ea9962dcf348513f3228bba4e48e217bcd75235cea22d63a1bc874fc3e3ef3baa0c7edaaf0bcedafa54e582131e1b09478edfb80218707f92c0";
|
|
2173
2329
|
function getBackupApiKeys() {
|
|
2174
2330
|
try {
|
|
2175
2331
|
const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
|
|
@@ -2207,6 +2363,26 @@ Return the response strictly as a JSON object with this shape, and absolutely no
|
|
|
2207
2363
|
"explanation": "Internal status message...",
|
|
2208
2364
|
"commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
|
|
2209
2365
|
}`;
|
|
2366
|
+
async function generateWithGemini(genAI, contents) {
|
|
2367
|
+
const models = [
|
|
2368
|
+
"gemini-flash-lite-latest",
|
|
2369
|
+
"gemini-3.5-flash-lite",
|
|
2370
|
+
"gemini-flash-latest",
|
|
2371
|
+
"gemini-3.8-flash"
|
|
2372
|
+
];
|
|
2373
|
+
let lastErr = null;
|
|
2374
|
+
for (const modelName of models) {
|
|
2375
|
+
try {
|
|
2376
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
2377
|
+
const result = await model.generateContent(contents);
|
|
2378
|
+
return result.response.text();
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
lastErr = err;
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
throw lastErr || new Error("All Gemini model fallbacks failed");
|
|
2385
|
+
}
|
|
2210
2386
|
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2211
2387
|
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2212
2388
|
const prompt = `
|
|
@@ -2251,9 +2427,7 @@ ${errorMsg}
|
|
|
2251
2427
|
try {
|
|
2252
2428
|
if (provider === "gemini") {
|
|
2253
2429
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2254
|
-
|
|
2255
|
-
const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2256
|
-
responseText = result.response.text();
|
|
2430
|
+
responseText = await generateWithGemini(genAI, [{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2257
2431
|
} else if (provider === "deepseek") {
|
|
2258
2432
|
const openai = new OpenAI({
|
|
2259
2433
|
apiKey,
|
|
@@ -2406,12 +2580,10 @@ ${content}
|
|
|
2406
2580
|
try {
|
|
2407
2581
|
if (provider === "gemini") {
|
|
2408
2582
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2409
|
-
|
|
2410
|
-
const result = await model.generateContent([
|
|
2583
|
+
responseText = await generateWithGemini(genAI, [
|
|
2411
2584
|
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2412
2585
|
{ text: prompt }
|
|
2413
2586
|
]);
|
|
2414
|
-
responseText = result.response.text();
|
|
2415
2587
|
}
|
|
2416
2588
|
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
2417
2589
|
const parsed = JSON.parse(responseText);
|
|
@@ -2464,9 +2636,8 @@ ${truncatedDiff}`;
|
|
|
2464
2636
|
try {
|
|
2465
2637
|
if (provider === "gemini") {
|
|
2466
2638
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
return result.response.text().trim();
|
|
2639
|
+
const text = await generateWithGemini(genAI, [{ text: systemPrompt }, { text: prompt }]);
|
|
2640
|
+
return text.trim();
|
|
2470
2641
|
}
|
|
2471
2642
|
} catch (e) {
|
|
2472
2643
|
console.warn(`[AI Service] Provider ${provider} commit gen failed: ${e.message}`);
|
|
@@ -2511,6 +2682,22 @@ let globalOnResolveComplete = null;
|
|
|
2511
2682
|
let globalOnMassiveProgress = null;
|
|
2512
2683
|
const DEBOUNCE_MS = 3e3;
|
|
2513
2684
|
const changedFiles = /* @__PURE__ */ new Set();
|
|
2685
|
+
function stopAutoSync() {
|
|
2686
|
+
isAutoSyncEnabled = false;
|
|
2687
|
+
if (currentWatcher) {
|
|
2688
|
+
currentWatcher.close();
|
|
2689
|
+
currentWatcher = null;
|
|
2690
|
+
}
|
|
2691
|
+
if (syncTimeout) {
|
|
2692
|
+
clearTimeout(syncTimeout);
|
|
2693
|
+
syncTimeout = null;
|
|
2694
|
+
}
|
|
2695
|
+
if (remotePollInterval) {
|
|
2696
|
+
clearInterval(remotePollInterval);
|
|
2697
|
+
remotePollInterval = null;
|
|
2698
|
+
}
|
|
2699
|
+
changedFiles.clear();
|
|
2700
|
+
}
|
|
2514
2701
|
function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete, onMassiveProgress) {
|
|
2515
2702
|
isAutoSyncEnabled = enabled;
|
|
2516
2703
|
globalOnLog = onLog;
|
|
@@ -2554,9 +2741,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2554
2741
|
}, 25e3);
|
|
2555
2742
|
currentWatcher = chokidar.watch(repoPath, {
|
|
2556
2743
|
ignored: [
|
|
2557
|
-
/(^|[
|
|
2744
|
+
/(^|[/\\])\.git([/\\]|$)/,
|
|
2558
2745
|
// strictly ignore .git directory
|
|
2559
|
-
/(^|[
|
|
2746
|
+
/(^|[/\\])\.DS_Store$/,
|
|
2560
2747
|
// strictly ignore .DS_Store files
|
|
2561
2748
|
/node_modules/,
|
|
2562
2749
|
/dist/,
|
|
@@ -2664,10 +2851,10 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2664
2851
|
}
|
|
2665
2852
|
if (errorMsg.includes("index.lock")) {
|
|
2666
2853
|
onLog("Git lock detected. Automatically clearing zombie lock file...");
|
|
2667
|
-
const lockPath =
|
|
2854
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
2668
2855
|
try {
|
|
2669
|
-
await
|
|
2670
|
-
} catch
|
|
2856
|
+
await fs__namespace.unlink(lockPath);
|
|
2857
|
+
} catch {
|
|
2671
2858
|
}
|
|
2672
2859
|
isResolving = false;
|
|
2673
2860
|
return;
|
|
@@ -2676,7 +2863,11 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2676
2863
|
onLog("Pulling remote changes with rebase...");
|
|
2677
2864
|
const status2 = await getStatus(sessionId);
|
|
2678
2865
|
const currentBranch = status2.current || "main";
|
|
2679
|
-
await git.pull("origin", currentBranch, {
|
|
2866
|
+
await git.pull("origin", currentBranch, {
|
|
2867
|
+
"--rebase": "false",
|
|
2868
|
+
"--autostash": null,
|
|
2869
|
+
"-X": "ours"
|
|
2870
|
+
});
|
|
2680
2871
|
onLog("Pull successful, attempting push again...");
|
|
2681
2872
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2682
2873
|
onLog("✅ Error resolved and push resumed!");
|
|
@@ -2705,7 +2896,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2705
2896
|
try {
|
|
2706
2897
|
const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
|
|
2707
2898
|
conflictedFilesContext[f] = content;
|
|
2708
|
-
} catch
|
|
2899
|
+
} catch {
|
|
2709
2900
|
}
|
|
2710
2901
|
}
|
|
2711
2902
|
try {
|
|
@@ -2769,7 +2960,7 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2769
2960
|
}
|
|
2770
2961
|
try {
|
|
2771
2962
|
await git.rebase({ "--continue": null });
|
|
2772
|
-
} catch
|
|
2963
|
+
} catch {
|
|
2773
2964
|
await git.commit("Auto-resolved merge conflict by AI");
|
|
2774
2965
|
}
|
|
2775
2966
|
const status = await getStatus(sessionId);
|
|
@@ -2800,11 +2991,37 @@ if (process.platform === "darwin") {
|
|
|
2800
2991
|
"/usr/sbin",
|
|
2801
2992
|
"/sbin"
|
|
2802
2993
|
];
|
|
2803
|
-
const
|
|
2804
|
-
const
|
|
2805
|
-
|
|
2806
|
-
|
|
2994
|
+
const pathParts = (process.env.PATH || "").split(":").filter(Boolean);
|
|
2995
|
+
for (const p of macPaths) {
|
|
2996
|
+
if (!pathParts.includes(p)) {
|
|
2997
|
+
pathParts.unshift(p);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
process.env.PATH = pathParts.join(":");
|
|
3001
|
+
} else if (process.platform === "win32") {
|
|
3002
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
3003
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
3004
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
3005
|
+
const userProfile = process.env.USERPROFILE || "";
|
|
3006
|
+
const programData = process.env.ProgramData || "C:\\ProgramData";
|
|
3007
|
+
const winPaths = [
|
|
3008
|
+
path__namespace.join(programFiles, "Git", "cmd"),
|
|
3009
|
+
path__namespace.join(programFiles, "Git", "bin"),
|
|
3010
|
+
path__namespace.join(programFiles, "Git", "mingw64", "bin"),
|
|
3011
|
+
path__namespace.join(programFilesX86, "Git", "cmd"),
|
|
3012
|
+
path__namespace.join(programFilesX86, "Git", "bin"),
|
|
3013
|
+
path__namespace.join(localAppData, "Programs", "Git", "cmd"),
|
|
3014
|
+
path__namespace.join(localAppData, "Programs", "Git", "bin"),
|
|
3015
|
+
path__namespace.join(userProfile, "scoop", "shims"),
|
|
3016
|
+
path__namespace.join(programData, "chocolatey", "bin")
|
|
3017
|
+
];
|
|
3018
|
+
const pathParts = (process.env.PATH || "").split(";").filter(Boolean);
|
|
3019
|
+
for (const p of winPaths) {
|
|
3020
|
+
if (!pathParts.includes(p)) {
|
|
3021
|
+
pathParts.unshift(p);
|
|
3022
|
+
}
|
|
2807
3023
|
}
|
|
3024
|
+
process.env.PATH = pathParts.join(";");
|
|
2808
3025
|
}
|
|
2809
3026
|
if (process.platform === "win32") {
|
|
2810
3027
|
electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
|
@@ -2833,6 +3050,7 @@ function createWindow(initialPath) {
|
|
|
2833
3050
|
mainWindow.on("closed", () => {
|
|
2834
3051
|
cleanupSession(sessionId);
|
|
2835
3052
|
cleanupGitSession(sessionId);
|
|
3053
|
+
stopAutoSync();
|
|
2836
3054
|
});
|
|
2837
3055
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
|
2838
3056
|
electron.shell.openExternal(details.url);
|
|
@@ -2878,15 +3096,67 @@ electron.app.on("second-instance", (_event, commandLine, workingDirectory) => {
|
|
|
2878
3096
|
});
|
|
2879
3097
|
if (process.defaultApp) {
|
|
2880
3098
|
if (process.argv.length >= 2) {
|
|
2881
|
-
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [
|
|
2882
|
-
require("path").resolve(process.argv[1])
|
|
2883
|
-
]);
|
|
3099
|
+
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [path__namespace.resolve(process.argv[1])]);
|
|
2884
3100
|
}
|
|
2885
3101
|
} else {
|
|
2886
3102
|
electron.app.setAsDefaultProtocolClient("gitelectron");
|
|
2887
3103
|
}
|
|
2888
3104
|
electron.app.whenReady().then(async () => {
|
|
2889
3105
|
utils.electronApp.setAppUserModelId("com.electron");
|
|
3106
|
+
try {
|
|
3107
|
+
createSafeGit().raw(["config", "--global", "safe.directory", "*"]).catch(() => {
|
|
3108
|
+
});
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
if (process.platform === "darwin") {
|
|
3112
|
+
const template = [
|
|
3113
|
+
{
|
|
3114
|
+
label: electron.app.name,
|
|
3115
|
+
submenu: [
|
|
3116
|
+
{ role: "about" },
|
|
3117
|
+
{ type: "separator" },
|
|
3118
|
+
{ role: "services" },
|
|
3119
|
+
{ type: "separator" },
|
|
3120
|
+
{ role: "hide" },
|
|
3121
|
+
{ role: "hideOthers" },
|
|
3122
|
+
{ role: "unhide" },
|
|
3123
|
+
{ type: "separator" },
|
|
3124
|
+
{ role: "quit" }
|
|
3125
|
+
]
|
|
3126
|
+
},
|
|
3127
|
+
{
|
|
3128
|
+
label: "Edit",
|
|
3129
|
+
submenu: [
|
|
3130
|
+
{ role: "undo" },
|
|
3131
|
+
{ role: "redo" },
|
|
3132
|
+
{ type: "separator" },
|
|
3133
|
+
{ role: "cut" },
|
|
3134
|
+
{ role: "copy" },
|
|
3135
|
+
{ role: "paste" },
|
|
3136
|
+
{ role: "selectAll" }
|
|
3137
|
+
]
|
|
3138
|
+
},
|
|
3139
|
+
{
|
|
3140
|
+
label: "View",
|
|
3141
|
+
submenu: [
|
|
3142
|
+
{ role: "reload" },
|
|
3143
|
+
{ role: "forceReload" },
|
|
3144
|
+
{ role: "toggleDevTools" },
|
|
3145
|
+
{ type: "separator" },
|
|
3146
|
+
{ role: "resetZoom" },
|
|
3147
|
+
{ role: "zoomIn" },
|
|
3148
|
+
{ role: "zoomOut" },
|
|
3149
|
+
{ type: "separator" },
|
|
3150
|
+
{ role: "togglefullscreen" }
|
|
3151
|
+
]
|
|
3152
|
+
},
|
|
3153
|
+
{
|
|
3154
|
+
label: "Window",
|
|
3155
|
+
submenu: [{ role: "minimize" }, { role: "zoom" }, { type: "separator" }, { role: "front" }]
|
|
3156
|
+
}
|
|
3157
|
+
];
|
|
3158
|
+
electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template));
|
|
3159
|
+
}
|
|
2890
3160
|
await loadAccounts();
|
|
2891
3161
|
electron.app.on("browser-window-created", (_, window) => {
|
|
2892
3162
|
utils.optimizer.watchWindowShortcuts(window);
|
|
@@ -2919,25 +3189,33 @@ electron.app.whenReady().then(async () => {
|
|
|
2919
3189
|
if (result.canceled || result.filePaths.length === 0) {
|
|
2920
3190
|
return null;
|
|
2921
3191
|
}
|
|
2922
|
-
const
|
|
2923
|
-
const
|
|
3192
|
+
const chosenPath = result.filePaths[0];
|
|
3193
|
+
const rootPath = await findGitRoot(chosenPath) || chosenPath;
|
|
3194
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3195
|
+
recordSessionPath(sessionId, rootPath);
|
|
2924
3196
|
if (isRepo) {
|
|
2925
|
-
initGit(sessionId,
|
|
3197
|
+
initGit(sessionId, rootPath);
|
|
2926
3198
|
}
|
|
2927
|
-
await saveLastWorkspace(
|
|
2928
|
-
return { path:
|
|
3199
|
+
await saveLastWorkspace(rootPath);
|
|
3200
|
+
return { path: rootPath, isRepo };
|
|
2929
3201
|
});
|
|
2930
|
-
electron.ipcMain.handle("set-repo-path", async (event,
|
|
3202
|
+
electron.ipcMain.handle("set-repo-path", async (event, inputPath) => {
|
|
2931
3203
|
const sessionId = event.sender.id;
|
|
2932
|
-
|
|
3204
|
+
if (!inputPath) return { path: "", isRepo: false };
|
|
3205
|
+
const rootPath = await findGitRoot(inputPath) || inputPath;
|
|
3206
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3207
|
+
recordSessionPath(sessionId, rootPath);
|
|
2933
3208
|
if (isRepo) {
|
|
2934
|
-
initGit(sessionId,
|
|
3209
|
+
initGit(sessionId, rootPath);
|
|
2935
3210
|
}
|
|
2936
|
-
await saveLastWorkspace(
|
|
2937
|
-
return { path:
|
|
3211
|
+
await saveLastWorkspace(rootPath);
|
|
3212
|
+
return { path: rootPath, isRepo };
|
|
2938
3213
|
});
|
|
2939
3214
|
electron.ipcMain.handle("init-repo", async (event, path2) => {
|
|
2940
|
-
|
|
3215
|
+
recordSessionPath(event.sender.id, path2);
|
|
3216
|
+
const result = await initializeRepo(event.sender.id, path2);
|
|
3217
|
+
await saveLastWorkspace(path2);
|
|
3218
|
+
return result;
|
|
2941
3219
|
});
|
|
2942
3220
|
electron.ipcMain.handle("get-status", async (event) => {
|
|
2943
3221
|
return await getStatus(event.sender.id);
|
|
@@ -3239,9 +3517,8 @@ electron.app.whenReady().then(async () => {
|
|
|
3239
3517
|
});
|
|
3240
3518
|
electron.ipcMain.handle("check-updates", async () => {
|
|
3241
3519
|
try {
|
|
3242
|
-
const https = require("https");
|
|
3243
3520
|
return new Promise((resolve) => {
|
|
3244
|
-
|
|
3521
|
+
https__namespace.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
|
|
3245
3522
|
let data = "";
|
|
3246
3523
|
res.on("data", (chunk) => {
|
|
3247
3524
|
data += chunk;
|
|
@@ -3250,19 +3527,18 @@ electron.app.whenReady().then(async () => {
|
|
|
3250
3527
|
try {
|
|
3251
3528
|
const json = JSON.parse(data);
|
|
3252
3529
|
resolve(json.version || null);
|
|
3253
|
-
} catch
|
|
3530
|
+
} catch {
|
|
3254
3531
|
resolve(null);
|
|
3255
3532
|
}
|
|
3256
3533
|
});
|
|
3257
3534
|
}).on("error", () => resolve(null));
|
|
3258
3535
|
});
|
|
3259
|
-
} catch
|
|
3536
|
+
} catch {
|
|
3260
3537
|
return null;
|
|
3261
3538
|
}
|
|
3262
3539
|
});
|
|
3263
3540
|
electron.ipcMain.handle("install-update", async () => {
|
|
3264
3541
|
return new Promise((resolve) => {
|
|
3265
|
-
const { spawn } = require("child_process");
|
|
3266
3542
|
if (process.platform === "win32") {
|
|
3267
3543
|
const cmdArgs = [
|
|
3268
3544
|
"/c",
|
|
@@ -3271,14 +3547,14 @@ electron.app.whenReady().then(async () => {
|
|
|
3271
3547
|
"/c",
|
|
3272
3548
|
"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
3549
|
];
|
|
3274
|
-
const child = spawn("cmd.exe", cmdArgs, {
|
|
3550
|
+
const child = child_process.spawn("cmd.exe", cmdArgs, {
|
|
3275
3551
|
detached: true,
|
|
3276
3552
|
stdio: "ignore"
|
|
3277
3553
|
});
|
|
3278
3554
|
child.unref();
|
|
3279
3555
|
} else {
|
|
3280
3556
|
const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
|
|
3281
|
-
const child = spawn("sh", ["-c", shScript], {
|
|
3557
|
+
const child = child_process.spawn("sh", ["-c", shScript], {
|
|
3282
3558
|
detached: true,
|
|
3283
3559
|
stdio: "ignore"
|
|
3284
3560
|
});
|