gitxelectron 1.4.1 → 1.4.2
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;
|
|
755
865
|
}
|
|
756
|
-
return false;
|
|
757
866
|
} catch {
|
|
758
|
-
|
|
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
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} catch {
|
|
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
|
|
@@ -2207,6 +2363,21 @@ 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 = ["gemini-2.0-flash", "gemini-1.5-flash-latest", "gemini-1.5-flash"];
|
|
2368
|
+
let lastErr = null;
|
|
2369
|
+
for (const modelName of models) {
|
|
2370
|
+
try {
|
|
2371
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
2372
|
+
const result = await model.generateContent(contents);
|
|
2373
|
+
return result.response.text();
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
lastErr = err;
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
throw lastErr || new Error("All Gemini model fallbacks failed");
|
|
2380
|
+
}
|
|
2210
2381
|
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2211
2382
|
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2212
2383
|
const prompt = `
|
|
@@ -2251,9 +2422,7 @@ ${errorMsg}
|
|
|
2251
2422
|
try {
|
|
2252
2423
|
if (provider === "gemini") {
|
|
2253
2424
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2254
|
-
|
|
2255
|
-
const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2256
|
-
responseText = result.response.text();
|
|
2425
|
+
responseText = await generateWithGemini(genAI, [{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2257
2426
|
} else if (provider === "deepseek") {
|
|
2258
2427
|
const openai = new OpenAI({
|
|
2259
2428
|
apiKey,
|
|
@@ -2406,12 +2575,10 @@ ${content}
|
|
|
2406
2575
|
try {
|
|
2407
2576
|
if (provider === "gemini") {
|
|
2408
2577
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2409
|
-
|
|
2410
|
-
const result = await model.generateContent([
|
|
2578
|
+
responseText = await generateWithGemini(genAI, [
|
|
2411
2579
|
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2412
2580
|
{ text: prompt }
|
|
2413
2581
|
]);
|
|
2414
|
-
responseText = result.response.text();
|
|
2415
2582
|
}
|
|
2416
2583
|
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
2417
2584
|
const parsed = JSON.parse(responseText);
|
|
@@ -2464,9 +2631,8 @@ ${truncatedDiff}`;
|
|
|
2464
2631
|
try {
|
|
2465
2632
|
if (provider === "gemini") {
|
|
2466
2633
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
return result.response.text().trim();
|
|
2634
|
+
const text = await generateWithGemini(genAI, [{ text: systemPrompt }, { text: prompt }]);
|
|
2635
|
+
return text.trim();
|
|
2470
2636
|
}
|
|
2471
2637
|
} catch (e) {
|
|
2472
2638
|
console.warn(`[AI Service] Provider ${provider} commit gen failed: ${e.message}`);
|
|
@@ -2511,6 +2677,22 @@ let globalOnResolveComplete = null;
|
|
|
2511
2677
|
let globalOnMassiveProgress = null;
|
|
2512
2678
|
const DEBOUNCE_MS = 3e3;
|
|
2513
2679
|
const changedFiles = /* @__PURE__ */ new Set();
|
|
2680
|
+
function stopAutoSync() {
|
|
2681
|
+
isAutoSyncEnabled = false;
|
|
2682
|
+
if (currentWatcher) {
|
|
2683
|
+
currentWatcher.close();
|
|
2684
|
+
currentWatcher = null;
|
|
2685
|
+
}
|
|
2686
|
+
if (syncTimeout) {
|
|
2687
|
+
clearTimeout(syncTimeout);
|
|
2688
|
+
syncTimeout = null;
|
|
2689
|
+
}
|
|
2690
|
+
if (remotePollInterval) {
|
|
2691
|
+
clearInterval(remotePollInterval);
|
|
2692
|
+
remotePollInterval = null;
|
|
2693
|
+
}
|
|
2694
|
+
changedFiles.clear();
|
|
2695
|
+
}
|
|
2514
2696
|
function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete, onMassiveProgress) {
|
|
2515
2697
|
isAutoSyncEnabled = enabled;
|
|
2516
2698
|
globalOnLog = onLog;
|
|
@@ -2554,9 +2736,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2554
2736
|
}, 25e3);
|
|
2555
2737
|
currentWatcher = chokidar.watch(repoPath, {
|
|
2556
2738
|
ignored: [
|
|
2557
|
-
/(^|[
|
|
2739
|
+
/(^|[/\\])\.git([/\\]|$)/,
|
|
2558
2740
|
// strictly ignore .git directory
|
|
2559
|
-
/(^|[
|
|
2741
|
+
/(^|[/\\])\.DS_Store$/,
|
|
2560
2742
|
// strictly ignore .DS_Store files
|
|
2561
2743
|
/node_modules/,
|
|
2562
2744
|
/dist/,
|
|
@@ -2664,10 +2846,10 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2664
2846
|
}
|
|
2665
2847
|
if (errorMsg.includes("index.lock")) {
|
|
2666
2848
|
onLog("Git lock detected. Automatically clearing zombie lock file...");
|
|
2667
|
-
const lockPath =
|
|
2849
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
2668
2850
|
try {
|
|
2669
|
-
await
|
|
2670
|
-
} catch
|
|
2851
|
+
await fs__namespace.unlink(lockPath);
|
|
2852
|
+
} catch {
|
|
2671
2853
|
}
|
|
2672
2854
|
isResolving = false;
|
|
2673
2855
|
return;
|
|
@@ -2676,7 +2858,11 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2676
2858
|
onLog("Pulling remote changes with rebase...");
|
|
2677
2859
|
const status2 = await getStatus(sessionId);
|
|
2678
2860
|
const currentBranch = status2.current || "main";
|
|
2679
|
-
await git.pull("origin", currentBranch, {
|
|
2861
|
+
await git.pull("origin", currentBranch, {
|
|
2862
|
+
"--rebase": "false",
|
|
2863
|
+
"--autostash": null,
|
|
2864
|
+
"-X": "ours"
|
|
2865
|
+
});
|
|
2680
2866
|
onLog("Pull successful, attempting push again...");
|
|
2681
2867
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2682
2868
|
onLog("✅ Error resolved and push resumed!");
|
|
@@ -2705,7 +2891,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2705
2891
|
try {
|
|
2706
2892
|
const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
|
|
2707
2893
|
conflictedFilesContext[f] = content;
|
|
2708
|
-
} catch
|
|
2894
|
+
} catch {
|
|
2709
2895
|
}
|
|
2710
2896
|
}
|
|
2711
2897
|
try {
|
|
@@ -2769,7 +2955,7 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2769
2955
|
}
|
|
2770
2956
|
try {
|
|
2771
2957
|
await git.rebase({ "--continue": null });
|
|
2772
|
-
} catch
|
|
2958
|
+
} catch {
|
|
2773
2959
|
await git.commit("Auto-resolved merge conflict by AI");
|
|
2774
2960
|
}
|
|
2775
2961
|
const status = await getStatus(sessionId);
|
|
@@ -2800,11 +2986,37 @@ if (process.platform === "darwin") {
|
|
|
2800
2986
|
"/usr/sbin",
|
|
2801
2987
|
"/sbin"
|
|
2802
2988
|
];
|
|
2803
|
-
const
|
|
2804
|
-
const
|
|
2805
|
-
|
|
2806
|
-
|
|
2989
|
+
const pathParts = (process.env.PATH || "").split(":").filter(Boolean);
|
|
2990
|
+
for (const p of macPaths) {
|
|
2991
|
+
if (!pathParts.includes(p)) {
|
|
2992
|
+
pathParts.unshift(p);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
process.env.PATH = pathParts.join(":");
|
|
2996
|
+
} else if (process.platform === "win32") {
|
|
2997
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
2998
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
2999
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
3000
|
+
const userProfile = process.env.USERPROFILE || "";
|
|
3001
|
+
const programData = process.env.ProgramData || "C:\\ProgramData";
|
|
3002
|
+
const winPaths = [
|
|
3003
|
+
path__namespace.join(programFiles, "Git", "cmd"),
|
|
3004
|
+
path__namespace.join(programFiles, "Git", "bin"),
|
|
3005
|
+
path__namespace.join(programFiles, "Git", "mingw64", "bin"),
|
|
3006
|
+
path__namespace.join(programFilesX86, "Git", "cmd"),
|
|
3007
|
+
path__namespace.join(programFilesX86, "Git", "bin"),
|
|
3008
|
+
path__namespace.join(localAppData, "Programs", "Git", "cmd"),
|
|
3009
|
+
path__namespace.join(localAppData, "Programs", "Git", "bin"),
|
|
3010
|
+
path__namespace.join(userProfile, "scoop", "shims"),
|
|
3011
|
+
path__namespace.join(programData, "chocolatey", "bin")
|
|
3012
|
+
];
|
|
3013
|
+
const pathParts = (process.env.PATH || "").split(";").filter(Boolean);
|
|
3014
|
+
for (const p of winPaths) {
|
|
3015
|
+
if (!pathParts.includes(p)) {
|
|
3016
|
+
pathParts.unshift(p);
|
|
3017
|
+
}
|
|
2807
3018
|
}
|
|
3019
|
+
process.env.PATH = pathParts.join(";");
|
|
2808
3020
|
}
|
|
2809
3021
|
if (process.platform === "win32") {
|
|
2810
3022
|
electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
|
@@ -2833,6 +3045,7 @@ function createWindow(initialPath) {
|
|
|
2833
3045
|
mainWindow.on("closed", () => {
|
|
2834
3046
|
cleanupSession(sessionId);
|
|
2835
3047
|
cleanupGitSession(sessionId);
|
|
3048
|
+
stopAutoSync();
|
|
2836
3049
|
});
|
|
2837
3050
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
|
2838
3051
|
electron.shell.openExternal(details.url);
|
|
@@ -2878,15 +3091,67 @@ electron.app.on("second-instance", (_event, commandLine, workingDirectory) => {
|
|
|
2878
3091
|
});
|
|
2879
3092
|
if (process.defaultApp) {
|
|
2880
3093
|
if (process.argv.length >= 2) {
|
|
2881
|
-
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [
|
|
2882
|
-
require("path").resolve(process.argv[1])
|
|
2883
|
-
]);
|
|
3094
|
+
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [path__namespace.resolve(process.argv[1])]);
|
|
2884
3095
|
}
|
|
2885
3096
|
} else {
|
|
2886
3097
|
electron.app.setAsDefaultProtocolClient("gitelectron");
|
|
2887
3098
|
}
|
|
2888
3099
|
electron.app.whenReady().then(async () => {
|
|
2889
3100
|
utils.electronApp.setAppUserModelId("com.electron");
|
|
3101
|
+
try {
|
|
3102
|
+
createSafeGit().raw(["config", "--global", "safe.directory", "*"]).catch(() => {
|
|
3103
|
+
});
|
|
3104
|
+
} catch {
|
|
3105
|
+
}
|
|
3106
|
+
if (process.platform === "darwin") {
|
|
3107
|
+
const template = [
|
|
3108
|
+
{
|
|
3109
|
+
label: electron.app.name,
|
|
3110
|
+
submenu: [
|
|
3111
|
+
{ role: "about" },
|
|
3112
|
+
{ type: "separator" },
|
|
3113
|
+
{ role: "services" },
|
|
3114
|
+
{ type: "separator" },
|
|
3115
|
+
{ role: "hide" },
|
|
3116
|
+
{ role: "hideOthers" },
|
|
3117
|
+
{ role: "unhide" },
|
|
3118
|
+
{ type: "separator" },
|
|
3119
|
+
{ role: "quit" }
|
|
3120
|
+
]
|
|
3121
|
+
},
|
|
3122
|
+
{
|
|
3123
|
+
label: "Edit",
|
|
3124
|
+
submenu: [
|
|
3125
|
+
{ role: "undo" },
|
|
3126
|
+
{ role: "redo" },
|
|
3127
|
+
{ type: "separator" },
|
|
3128
|
+
{ role: "cut" },
|
|
3129
|
+
{ role: "copy" },
|
|
3130
|
+
{ role: "paste" },
|
|
3131
|
+
{ role: "selectAll" }
|
|
3132
|
+
]
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
label: "View",
|
|
3136
|
+
submenu: [
|
|
3137
|
+
{ role: "reload" },
|
|
3138
|
+
{ role: "forceReload" },
|
|
3139
|
+
{ role: "toggleDevTools" },
|
|
3140
|
+
{ type: "separator" },
|
|
3141
|
+
{ role: "resetZoom" },
|
|
3142
|
+
{ role: "zoomIn" },
|
|
3143
|
+
{ role: "zoomOut" },
|
|
3144
|
+
{ type: "separator" },
|
|
3145
|
+
{ role: "togglefullscreen" }
|
|
3146
|
+
]
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
label: "Window",
|
|
3150
|
+
submenu: [{ role: "minimize" }, { role: "zoom" }, { type: "separator" }, { role: "front" }]
|
|
3151
|
+
}
|
|
3152
|
+
];
|
|
3153
|
+
electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template));
|
|
3154
|
+
}
|
|
2890
3155
|
await loadAccounts();
|
|
2891
3156
|
electron.app.on("browser-window-created", (_, window) => {
|
|
2892
3157
|
utils.optimizer.watchWindowShortcuts(window);
|
|
@@ -2919,25 +3184,33 @@ electron.app.whenReady().then(async () => {
|
|
|
2919
3184
|
if (result.canceled || result.filePaths.length === 0) {
|
|
2920
3185
|
return null;
|
|
2921
3186
|
}
|
|
2922
|
-
const
|
|
2923
|
-
const
|
|
3187
|
+
const chosenPath = result.filePaths[0];
|
|
3188
|
+
const rootPath = await findGitRoot(chosenPath) || chosenPath;
|
|
3189
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3190
|
+
recordSessionPath(sessionId, rootPath);
|
|
2924
3191
|
if (isRepo) {
|
|
2925
|
-
initGit(sessionId,
|
|
3192
|
+
initGit(sessionId, rootPath);
|
|
2926
3193
|
}
|
|
2927
|
-
await saveLastWorkspace(
|
|
2928
|
-
return { path:
|
|
3194
|
+
await saveLastWorkspace(rootPath);
|
|
3195
|
+
return { path: rootPath, isRepo };
|
|
2929
3196
|
});
|
|
2930
|
-
electron.ipcMain.handle("set-repo-path", async (event,
|
|
3197
|
+
electron.ipcMain.handle("set-repo-path", async (event, inputPath) => {
|
|
2931
3198
|
const sessionId = event.sender.id;
|
|
2932
|
-
|
|
3199
|
+
if (!inputPath) return { path: "", isRepo: false };
|
|
3200
|
+
const rootPath = await findGitRoot(inputPath) || inputPath;
|
|
3201
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3202
|
+
recordSessionPath(sessionId, rootPath);
|
|
2933
3203
|
if (isRepo) {
|
|
2934
|
-
initGit(sessionId,
|
|
3204
|
+
initGit(sessionId, rootPath);
|
|
2935
3205
|
}
|
|
2936
|
-
await saveLastWorkspace(
|
|
2937
|
-
return { path:
|
|
3206
|
+
await saveLastWorkspace(rootPath);
|
|
3207
|
+
return { path: rootPath, isRepo };
|
|
2938
3208
|
});
|
|
2939
3209
|
electron.ipcMain.handle("init-repo", async (event, path2) => {
|
|
2940
|
-
|
|
3210
|
+
recordSessionPath(event.sender.id, path2);
|
|
3211
|
+
const result = await initializeRepo(event.sender.id, path2);
|
|
3212
|
+
await saveLastWorkspace(path2);
|
|
3213
|
+
return result;
|
|
2941
3214
|
});
|
|
2942
3215
|
electron.ipcMain.handle("get-status", async (event) => {
|
|
2943
3216
|
return await getStatus(event.sender.id);
|
|
@@ -3239,9 +3512,8 @@ electron.app.whenReady().then(async () => {
|
|
|
3239
3512
|
});
|
|
3240
3513
|
electron.ipcMain.handle("check-updates", async () => {
|
|
3241
3514
|
try {
|
|
3242
|
-
const https = require("https");
|
|
3243
3515
|
return new Promise((resolve) => {
|
|
3244
|
-
|
|
3516
|
+
https__namespace.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
|
|
3245
3517
|
let data = "";
|
|
3246
3518
|
res.on("data", (chunk) => {
|
|
3247
3519
|
data += chunk;
|
|
@@ -3250,19 +3522,18 @@ electron.app.whenReady().then(async () => {
|
|
|
3250
3522
|
try {
|
|
3251
3523
|
const json = JSON.parse(data);
|
|
3252
3524
|
resolve(json.version || null);
|
|
3253
|
-
} catch
|
|
3525
|
+
} catch {
|
|
3254
3526
|
resolve(null);
|
|
3255
3527
|
}
|
|
3256
3528
|
});
|
|
3257
3529
|
}).on("error", () => resolve(null));
|
|
3258
3530
|
});
|
|
3259
|
-
} catch
|
|
3531
|
+
} catch {
|
|
3260
3532
|
return null;
|
|
3261
3533
|
}
|
|
3262
3534
|
});
|
|
3263
3535
|
electron.ipcMain.handle("install-update", async () => {
|
|
3264
3536
|
return new Promise((resolve) => {
|
|
3265
|
-
const { spawn } = require("child_process");
|
|
3266
3537
|
if (process.platform === "win32") {
|
|
3267
3538
|
const cmdArgs = [
|
|
3268
3539
|
"/c",
|
|
@@ -3271,14 +3542,14 @@ electron.app.whenReady().then(async () => {
|
|
|
3271
3542
|
"/c",
|
|
3272
3543
|
"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
3544
|
];
|
|
3274
|
-
const child = spawn("cmd.exe", cmdArgs, {
|
|
3545
|
+
const child = child_process.spawn("cmd.exe", cmdArgs, {
|
|
3275
3546
|
detached: true,
|
|
3276
3547
|
stdio: "ignore"
|
|
3277
3548
|
});
|
|
3278
3549
|
child.unref();
|
|
3279
3550
|
} else {
|
|
3280
3551
|
const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
|
|
3281
|
-
const child = spawn("sh", ["-c", shScript], {
|
|
3552
|
+
const child = child_process.spawn("sh", ["-c", shScript], {
|
|
3282
3553
|
detached: true,
|
|
3283
3554
|
stdio: "ignore"
|
|
3284
3555
|
});
|