gitxelectron 1.3.9 → 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/bin/ai-setup.js +46 -0
- package/bin/cli.js +35 -13
- package/out/main/index.js +730 -303
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/{index-Dtxyy2BB.js → index-7yJPiur0.js} +1713 -626
- package/out/renderer/assets/index-CI-_rJcn.css +2989 -0
- package/out/renderer/index.html +14 -11
- package/package.json +2 -1
- package/out/renderer/assets/index-Bhk4vMRr.css +0 -2989
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();
|
|
@@ -111,8 +114,24 @@ async function logout(sessionId) {
|
|
|
111
114
|
}
|
|
112
115
|
async function getAuthStatus(sessionId) {
|
|
113
116
|
const activeAccount = getActiveAccount(sessionId);
|
|
114
|
-
if (!activeAccount)
|
|
115
|
-
|
|
117
|
+
if (!activeAccount) {
|
|
118
|
+
return {
|
|
119
|
+
status: "Not Authenticated",
|
|
120
|
+
username: null,
|
|
121
|
+
avatarUrl: null,
|
|
122
|
+
authMethod: "Local Git Credentials",
|
|
123
|
+
aiProvider: "GitHub Models",
|
|
124
|
+
securityStatus: "Encrypted Local Keystore"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
status: "Verified",
|
|
129
|
+
username: activeAccount.username,
|
|
130
|
+
avatarUrl: activeAccount.avatar_url || null,
|
|
131
|
+
authMethod: "GitHub OAuth (Verified)",
|
|
132
|
+
aiProvider: "GitHub Models (gpt-4o-mini)",
|
|
133
|
+
securityStatus: "End-to-End Encrypted Session"
|
|
134
|
+
};
|
|
116
135
|
}
|
|
117
136
|
async function startDeviceFlow(_sessionId) {
|
|
118
137
|
const response = await fetch("https://github.com/login/device/code", {
|
|
@@ -379,6 +398,25 @@ async function createGitHubRepository(sessionId, name, description, isPrivate, a
|
|
|
379
398
|
});
|
|
380
399
|
if (!res.ok) {
|
|
381
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
|
+
}
|
|
382
420
|
throw new Error(errorData.message || "Failed to create repository");
|
|
383
421
|
}
|
|
384
422
|
return await res.json();
|
|
@@ -586,22 +624,78 @@ async function declineInvitation(sessionId, invitationId) {
|
|
|
586
624
|
}
|
|
587
625
|
}
|
|
588
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
|
+
}
|
|
589
681
|
function initGit(sessionId, baseDir) {
|
|
682
|
+
recordSessionPath(sessionId, baseDir);
|
|
590
683
|
const options = {
|
|
591
684
|
baseDir,
|
|
592
|
-
binary:
|
|
685
|
+
binary: getGitBinary(),
|
|
593
686
|
maxConcurrentProcesses: 6,
|
|
594
687
|
unsafe: {
|
|
595
688
|
allowUnsafeEditor: true,
|
|
596
689
|
allowUnsafeAskPass: true,
|
|
597
690
|
allowUnsafeCredentialHelper: true,
|
|
598
|
-
allowUnsafeFilter: true
|
|
691
|
+
allowUnsafeFilter: true,
|
|
692
|
+
allowUnsafeCustomBinary: true
|
|
599
693
|
},
|
|
600
694
|
progress: ({ method, stage, progress }) => {
|
|
601
695
|
try {
|
|
602
696
|
const wc = electron.webContents.fromId(sessionId);
|
|
603
697
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
604
|
-
} catch
|
|
698
|
+
} catch {
|
|
605
699
|
}
|
|
606
700
|
}
|
|
607
701
|
};
|
|
@@ -612,6 +706,8 @@ function initGit(sessionId, baseDir) {
|
|
|
612
706
|
GIT_ASKPASS: "echo"
|
|
613
707
|
});
|
|
614
708
|
gitSessions.set(sessionId, { git, repoPath: baseDir });
|
|
709
|
+
checkAndHealZombieGitState(baseDir).catch(() => {
|
|
710
|
+
});
|
|
615
711
|
(async () => {
|
|
616
712
|
try {
|
|
617
713
|
const name = await git.getConfig("user.name");
|
|
@@ -629,20 +725,21 @@ function initGit(sessionId, baseDir) {
|
|
|
629
725
|
}
|
|
630
726
|
function createSafeGit(baseDir, sessionId) {
|
|
631
727
|
const options = {
|
|
632
|
-
binary:
|
|
728
|
+
binary: getGitBinary(),
|
|
633
729
|
maxConcurrentProcesses: 6,
|
|
634
730
|
unsafe: {
|
|
635
731
|
allowUnsafeEditor: true,
|
|
636
732
|
allowUnsafeAskPass: true,
|
|
637
733
|
allowUnsafeCredentialHelper: true,
|
|
638
|
-
allowUnsafeFilter: true
|
|
734
|
+
allowUnsafeFilter: true,
|
|
735
|
+
allowUnsafeCustomBinary: true
|
|
639
736
|
},
|
|
640
737
|
progress: ({ method, stage, progress }) => {
|
|
641
738
|
if (!sessionId) return;
|
|
642
739
|
try {
|
|
643
740
|
const wc = electron.webContents.fromId(sessionId);
|
|
644
741
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
645
|
-
} catch
|
|
742
|
+
} catch {
|
|
646
743
|
}
|
|
647
744
|
}
|
|
648
745
|
};
|
|
@@ -658,9 +755,31 @@ function createSafeGit(baseDir, sessionId) {
|
|
|
658
755
|
}
|
|
659
756
|
function cleanupGitSession(sessionId) {
|
|
660
757
|
gitSessions.delete(sessionId);
|
|
758
|
+
sessionRepoPaths.delete(sessionId);
|
|
661
759
|
}
|
|
662
760
|
function getGitSession(sessionId) {
|
|
663
|
-
|
|
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
|
+
}
|
|
664
783
|
if (!session) throw new Error("Git not initialized for this window");
|
|
665
784
|
return session;
|
|
666
785
|
}
|
|
@@ -701,9 +820,9 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
701
820
|
let existing = "";
|
|
702
821
|
try {
|
|
703
822
|
existing = await fs__namespace.readFile(attrsPath, "utf8");
|
|
704
|
-
} catch
|
|
823
|
+
} catch {
|
|
705
824
|
}
|
|
706
|
-
|
|
825
|
+
const newLines = [];
|
|
707
826
|
for (const ext of DEFAULT_LFS_EXTENSIONS) {
|
|
708
827
|
if (!existing.includes(ext)) {
|
|
709
828
|
newLines.push(`${ext} filter=lfs diff=lfs merge=lfs -text`);
|
|
@@ -713,136 +832,228 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
713
832
|
const appendContent = (existing && !existing.endsWith("\n") ? "\n" : "") + newLines.join("\n") + "\n";
|
|
714
833
|
await fs__namespace.appendFile(attrsPath, appendContent);
|
|
715
834
|
}
|
|
716
|
-
} catch
|
|
835
|
+
} catch {
|
|
717
836
|
}
|
|
718
837
|
}
|
|
719
|
-
async function
|
|
720
|
-
|
|
838
|
+
async function findGitRoot(dir) {
|
|
839
|
+
if (!dir) return null;
|
|
840
|
+
const cleanDir = path__namespace.normalize(dir.trim());
|
|
721
841
|
try {
|
|
722
|
-
|
|
723
|
-
|
|
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
|
+
}
|
|
724
849
|
} catch {
|
|
725
|
-
hasGitFolder = false;
|
|
726
850
|
}
|
|
727
851
|
try {
|
|
728
|
-
const tempGit =
|
|
729
|
-
...process.env,
|
|
730
|
-
GCM_INTERACTIVE: "false",
|
|
731
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
732
|
-
GIT_ASKPASS: "echo"
|
|
733
|
-
});
|
|
852
|
+
const tempGit = createSafeGit(cleanDir);
|
|
734
853
|
const isRepo = await tempGit.checkIsRepo();
|
|
735
|
-
if (isRepo)
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
+
}
|
|
739
897
|
}
|
|
740
|
-
return false;
|
|
741
898
|
} catch {
|
|
742
|
-
|
|
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"))) {
|
|
743
907
|
try {
|
|
744
|
-
await
|
|
745
|
-
const tempGit = simpleGit.simpleGit(dir).env({
|
|
746
|
-
...process.env,
|
|
747
|
-
GCM_INTERACTIVE: "false",
|
|
748
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
749
|
-
GIT_ASKPASS: "echo"
|
|
750
|
-
});
|
|
751
|
-
return await tempGit.checkIsRepo();
|
|
908
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", cleanDir]);
|
|
752
909
|
} catch {
|
|
753
|
-
return true;
|
|
754
910
|
}
|
|
911
|
+
return true;
|
|
755
912
|
}
|
|
756
|
-
|
|
913
|
+
} catch {
|
|
757
914
|
}
|
|
915
|
+
const root = await findGitRoot(cleanDir);
|
|
916
|
+
return root !== null;
|
|
758
917
|
}
|
|
759
918
|
async function initializeRepo(sessionId, dir) {
|
|
760
919
|
try {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
unsafe: {
|
|
764
|
-
allowUnsafeEditor: true,
|
|
765
|
-
allowUnsafeAskPass: true,
|
|
766
|
-
allowUnsafeCredentialHelper: true
|
|
767
|
-
}
|
|
920
|
+
recordSessionPath(sessionId, dir);
|
|
921
|
+
await fs__namespace.mkdir(dir, { recursive: true }).catch(() => {
|
|
768
922
|
});
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
await tempGit.raw([
|
|
773
|
-
"-c",
|
|
774
|
-
"user.name=gitelectron",
|
|
775
|
-
"-c",
|
|
776
|
-
"user.email=noreply@gitelectron.local",
|
|
777
|
-
"commit",
|
|
778
|
-
"--allow-empty",
|
|
779
|
-
"-m",
|
|
780
|
-
"Initial repository setup"
|
|
781
|
-
]);
|
|
923
|
+
try {
|
|
924
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", dir]);
|
|
925
|
+
} catch {
|
|
782
926
|
}
|
|
783
|
-
|
|
927
|
+
const tempGit = createSafeGit(dir, sessionId);
|
|
928
|
+
await tempGit.init();
|
|
784
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
|
+
}
|
|
785
956
|
return true;
|
|
786
957
|
} catch (e) {
|
|
787
958
|
console.error("Failed to init repo", e);
|
|
788
|
-
|
|
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);
|
|
789
1009
|
}
|
|
790
1010
|
}
|
|
791
1011
|
async function getStatus(sessionId) {
|
|
792
1012
|
const { git, repoPath } = getGitSession(sessionId);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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 {
|
|
805
1044
|
}
|
|
806
1045
|
}
|
|
807
1046
|
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
created: [],
|
|
814
|
-
deleted: [],
|
|
815
|
-
modified: [],
|
|
816
|
-
conflicted: [],
|
|
817
|
-
renamed: [],
|
|
818
|
-
staged: [],
|
|
819
|
-
files: [],
|
|
820
|
-
current: "main",
|
|
821
|
-
ahead: 0,
|
|
822
|
-
behind: 0,
|
|
823
|
-
tracking: null,
|
|
824
|
-
isMassive: true,
|
|
825
|
-
totalChanges: count
|
|
826
|
-
});
|
|
827
|
-
} else {
|
|
828
|
-
try {
|
|
829
|
-
const status = await git.status();
|
|
830
|
-
const result = JSON.parse(JSON.stringify(status));
|
|
831
|
-
result.isMassive = false;
|
|
832
|
-
result.totalChanges = count;
|
|
833
|
-
if (!result.staged) result.staged = [];
|
|
834
|
-
resolve(result);
|
|
835
|
-
} catch (e) {
|
|
836
|
-
reject(e);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
child.on("error", reject);
|
|
841
|
-
});
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
result.totalChanges = result.files?.length || 0;
|
|
1051
|
+
return result;
|
|
842
1052
|
}
|
|
843
1053
|
async function stageFiles(sessionId, files) {
|
|
844
1054
|
const { git, repoPath } = getGitSession(sessionId);
|
|
845
1055
|
const fileArray = Array.isArray(files) ? files : [files];
|
|
1056
|
+
await ensureAiWeightsUseLfs(git);
|
|
846
1057
|
if (repoPath) {
|
|
847
1058
|
const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
|
|
848
1059
|
const largeFiles = [];
|
|
@@ -853,7 +1064,7 @@ async function stageFiles(sessionId, files) {
|
|
|
853
1064
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
854
1065
|
largeFiles.push(file);
|
|
855
1066
|
}
|
|
856
|
-
} catch
|
|
1067
|
+
} catch {
|
|
857
1068
|
}
|
|
858
1069
|
}
|
|
859
1070
|
if (largeFiles.length > 0) {
|
|
@@ -907,6 +1118,7 @@ async function unstageFiles(sessionId, files) {
|
|
|
907
1118
|
}
|
|
908
1119
|
async function commitChanges(sessionId, message) {
|
|
909
1120
|
const { git } = getGitSession(sessionId);
|
|
1121
|
+
await ensureAiWeightsUseLfs(git);
|
|
910
1122
|
try {
|
|
911
1123
|
const nameConfig = await git.getConfig("user.name");
|
|
912
1124
|
const emailConfig = await git.getConfig("user.email");
|
|
@@ -936,7 +1148,7 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
936
1148
|
parsed.password = token;
|
|
937
1149
|
return parsed.toString();
|
|
938
1150
|
}
|
|
939
|
-
} catch
|
|
1151
|
+
} catch {
|
|
940
1152
|
if (remoteUrl.includes("github.com")) {
|
|
941
1153
|
return remoteUrl.replace(
|
|
942
1154
|
/https:\/\/(?:[^@]+@)?github\.com\//,
|
|
@@ -947,10 +1159,11 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
947
1159
|
return remoteUrl;
|
|
948
1160
|
}
|
|
949
1161
|
async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3, onProgress, onDetailedProgress) {
|
|
950
|
-
const { repoPath } = getGitSession(sessionId);
|
|
1162
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1163
|
+
await ensureAiWeightsUseLfs(git);
|
|
951
1164
|
if (onProgress) onProgress("Starting rapid file count to prepare massive chunking...");
|
|
952
1165
|
const totalFiles = await new Promise((resolve) => {
|
|
953
|
-
const child2 = child_process.spawn(
|
|
1166
|
+
const child2 = child_process.spawn(getGitBinary(), ["status", "--porcelain"], { cwd: repoPath });
|
|
954
1167
|
let count = 0;
|
|
955
1168
|
child2.stdout.on("data", (chunk) => {
|
|
956
1169
|
for (let i = 0; i < chunk.length; ++i) {
|
|
@@ -965,7 +1178,6 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
965
1178
|
return true;
|
|
966
1179
|
}
|
|
967
1180
|
if (onProgress) onProgress(`Total files to push: ${totalFiles}. Starting chunked upload...`);
|
|
968
|
-
const { git } = getGitSession(sessionId);
|
|
969
1181
|
const remotes = await getRemotes(sessionId);
|
|
970
1182
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
971
1183
|
if (onProgress) onProgress("Starting massive repository push...");
|
|
@@ -976,7 +1188,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
976
1188
|
if (remoteObj && remoteObj.refs.push) {
|
|
977
1189
|
authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
978
1190
|
}
|
|
979
|
-
const child = child_process.spawn(
|
|
1191
|
+
const child = child_process.spawn(getGitBinary(), ["status", "--porcelain", "-z"], { cwd: repoPath });
|
|
980
1192
|
let buffer = "";
|
|
981
1193
|
let currentChunk = [];
|
|
982
1194
|
let chunkCount = 0;
|
|
@@ -1018,7 +1230,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1018
1230
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
1019
1231
|
largeFiles.push(file);
|
|
1020
1232
|
}
|
|
1021
|
-
} catch
|
|
1233
|
+
} catch {
|
|
1022
1234
|
}
|
|
1023
1235
|
})
|
|
1024
1236
|
);
|
|
@@ -1055,7 +1267,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1055
1267
|
} finally {
|
|
1056
1268
|
try {
|
|
1057
1269
|
await fs__namespace.unlink(tempFile);
|
|
1058
|
-
} catch
|
|
1270
|
+
} catch {
|
|
1059
1271
|
}
|
|
1060
1272
|
}
|
|
1061
1273
|
};
|
|
@@ -1083,7 +1295,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1083
1295
|
if (onProgress) onProgress("Committing all files...");
|
|
1084
1296
|
try {
|
|
1085
1297
|
await git.commit(`Auto-sync (Massive Push)`);
|
|
1086
|
-
} catch
|
|
1298
|
+
} catch {
|
|
1087
1299
|
}
|
|
1088
1300
|
if (onProgress) onProgress("Pushing to remote...");
|
|
1089
1301
|
const pushArgs = [
|
|
@@ -1100,6 +1312,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1100
1312
|
"-c",
|
|
1101
1313
|
"lfs.concurrenttransfers=8",
|
|
1102
1314
|
"push",
|
|
1315
|
+
"--set-upstream",
|
|
1103
1316
|
"--progress",
|
|
1104
1317
|
"--no-verify"
|
|
1105
1318
|
];
|
|
@@ -1108,7 +1321,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1108
1321
|
} catch (err) {
|
|
1109
1322
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1110
1323
|
if (onProgress) onProgress("Remote branch is ahead. Pulling changes...");
|
|
1111
|
-
await git.pull(authUrl, branch, { "--rebase":
|
|
1324
|
+
await git.pull(authUrl, branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1112
1325
|
if (onProgress) onProgress("Pushing to remote after pull...");
|
|
1113
1326
|
await git.raw([...pushArgs, authUrl, branch]);
|
|
1114
1327
|
} else {
|
|
@@ -1123,15 +1336,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1123
1336
|
async function pushToRemote(sessionId, remote, branch) {
|
|
1124
1337
|
const remotes = await getRemotes(sessionId);
|
|
1125
1338
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1126
|
-
const tempGit =
|
|
1127
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1128
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1129
|
-
}).env({
|
|
1130
|
-
...process.env,
|
|
1131
|
-
GCM_INTERACTIVE: "false",
|
|
1132
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1133
|
-
GIT_ASKPASS: "echo"
|
|
1134
|
-
});
|
|
1339
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1135
1340
|
const doPush = async () => {
|
|
1136
1341
|
const pushArgs = [
|
|
1137
1342
|
"-c",
|
|
@@ -1147,6 +1352,7 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1147
1352
|
"-c",
|
|
1148
1353
|
"lfs.concurrenttransfers=8",
|
|
1149
1354
|
"push",
|
|
1355
|
+
"--set-upstream",
|
|
1150
1356
|
"--progress",
|
|
1151
1357
|
"--no-verify"
|
|
1152
1358
|
];
|
|
@@ -1162,7 +1368,7 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1162
1368
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1163
1369
|
console.log("Push rejected because branch is behind. Auto-pulling with rebase...");
|
|
1164
1370
|
const { git } = getGitSession(sessionId);
|
|
1165
|
-
const options = { "--rebase":
|
|
1371
|
+
const options = { "--rebase": "false", "--autostash": null, "-X": "ours" };
|
|
1166
1372
|
if (remoteObj && remoteObj.refs.fetch) {
|
|
1167
1373
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
|
|
1168
1374
|
await git.pull(authUrl, branch, options);
|
|
@@ -1175,10 +1381,40 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1175
1381
|
}
|
|
1176
1382
|
}
|
|
1177
1383
|
async function masterForcePush(sessionId, remote, branch) {
|
|
1178
|
-
const { git } = getGitSession(sessionId);
|
|
1384
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1179
1385
|
const remotes = await getRemotes(sessionId);
|
|
1180
|
-
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1181
|
-
|
|
1386
|
+
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1387
|
+
try {
|
|
1388
|
+
const branchSummary = await git.branchLocal();
|
|
1389
|
+
let current = branchSummary.current || branch;
|
|
1390
|
+
if (current && current.startsWith("__master_push_temp_")) {
|
|
1391
|
+
const candidates = ["main", "master", branch].filter(
|
|
1392
|
+
(b) => b && !b.startsWith("__master_push_temp_") && branchSummary.all.includes(b)
|
|
1393
|
+
);
|
|
1394
|
+
const fallbackBranch = candidates[0] || "main";
|
|
1395
|
+
try {
|
|
1396
|
+
await git.checkout(fallbackBranch);
|
|
1397
|
+
current = fallbackBranch;
|
|
1398
|
+
branch = fallbackBranch;
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
for (const b of branchSummary.all) {
|
|
1403
|
+
if (b.startsWith("__master_push_temp_") && b !== current) {
|
|
1404
|
+
try {
|
|
1405
|
+
await git.deleteLocalBranch(b, true);
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
await ensureAiWeightsUseLfs(git);
|
|
1413
|
+
try {
|
|
1414
|
+
await git.add(".");
|
|
1415
|
+
} catch {
|
|
1416
|
+
await git.raw(["add", "-A"]);
|
|
1417
|
+
}
|
|
1182
1418
|
try {
|
|
1183
1419
|
const name = await git.getConfig("user.name");
|
|
1184
1420
|
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
@@ -1188,34 +1424,31 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1188
1424
|
console.warn("Failed to ensure git identity before master push:", e);
|
|
1189
1425
|
}
|
|
1190
1426
|
try {
|
|
1191
|
-
await git.commit("Master Force Push -
|
|
1427
|
+
await git.commit("Master Force Push - Sync changes");
|
|
1192
1428
|
} catch (e) {
|
|
1193
1429
|
if (!e.message.includes("nothing to commit")) {
|
|
1194
|
-
|
|
1430
|
+
console.warn("Commit note:", e.message);
|
|
1195
1431
|
}
|
|
1196
1432
|
}
|
|
1197
|
-
const tempGit =
|
|
1198
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1199
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1200
|
-
}).env({
|
|
1201
|
-
...process.env,
|
|
1202
|
-
GCM_INTERACTIVE: "false",
|
|
1203
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1204
|
-
GIT_ASKPASS: "echo"
|
|
1205
|
-
});
|
|
1433
|
+
const tempGit = createSafeGit(repoPath, sessionId);
|
|
1206
1434
|
const pushArgs = [
|
|
1207
1435
|
"-c",
|
|
1208
1436
|
"credential.helper=",
|
|
1437
|
+
"-c",
|
|
1438
|
+
"filter.lfs.required=false",
|
|
1439
|
+
"-c",
|
|
1440
|
+
"lfs.allowincompletepush=true",
|
|
1209
1441
|
"push",
|
|
1442
|
+
"--set-upstream",
|
|
1210
1443
|
"--progress",
|
|
1211
1444
|
"--no-verify",
|
|
1212
1445
|
"-f"
|
|
1213
1446
|
];
|
|
1214
|
-
if (remoteObj && remoteObj.refs
|
|
1447
|
+
if (remoteObj && remoteObj.refs?.push) {
|
|
1215
1448
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1216
1449
|
return await tempGit.raw([...pushArgs, authUrl, branch]);
|
|
1217
1450
|
}
|
|
1218
|
-
return await tempGit.raw([...pushArgs, remote, branch]);
|
|
1451
|
+
return await tempGit.raw([...pushArgs, remote || "origin", branch]);
|
|
1219
1452
|
}
|
|
1220
1453
|
async function pullFromRemote(sessionId, remote, branch) {
|
|
1221
1454
|
const { git, repoPath } = getGitSession(sessionId);
|
|
@@ -1280,11 +1513,13 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1280
1513
|
"pull",
|
|
1281
1514
|
"--no-rebase",
|
|
1282
1515
|
"--allow-unrelated-histories",
|
|
1516
|
+
"-X",
|
|
1517
|
+
"ours",
|
|
1283
1518
|
authUrl,
|
|
1284
1519
|
targetBranch
|
|
1285
1520
|
];
|
|
1286
1521
|
const pullOutput = await new Promise((resolve, reject) => {
|
|
1287
|
-
const child = child_process.spawn(
|
|
1522
|
+
const child = child_process.spawn(getGitBinary(), pullArgs, {
|
|
1288
1523
|
cwd: repoPath,
|
|
1289
1524
|
env: {
|
|
1290
1525
|
...process.env,
|
|
@@ -1359,7 +1594,7 @@ async function checkRemoteUpdates(sessionId) {
|
|
|
1359
1594
|
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
|
|
1360
1595
|
await new Promise((resolve) => {
|
|
1361
1596
|
const child = child_process.spawn(
|
|
1362
|
-
|
|
1597
|
+
getGitBinary(),
|
|
1363
1598
|
[
|
|
1364
1599
|
"-c",
|
|
1365
1600
|
"credential.helper=",
|
|
@@ -1417,19 +1652,7 @@ async function getBranches(sessionId) {
|
|
|
1417
1652
|
const remotes = await git.getRemotes(true);
|
|
1418
1653
|
if (remotes.length > 0) {
|
|
1419
1654
|
const remoteObj = remotes[0];
|
|
1420
|
-
const tempGit =
|
|
1421
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1422
|
-
unsafe: {
|
|
1423
|
-
allowUnsafeEditor: true,
|
|
1424
|
-
allowUnsafeAskPass: true,
|
|
1425
|
-
allowUnsafeCredentialHelper: true
|
|
1426
|
-
}
|
|
1427
|
-
}).env({
|
|
1428
|
-
...process.env,
|
|
1429
|
-
GCM_INTERACTIVE: "false",
|
|
1430
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1431
|
-
GIT_ASKPASS: "echo"
|
|
1432
|
-
});
|
|
1655
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1433
1656
|
const FETCH_TIMEOUT_MS = 15e3;
|
|
1434
1657
|
const fetchPromise = remoteObj.refs.fetch ? tempGit.fetch(injectAuthToken(sessionId, remoteObj.refs.fetch), { "--prune": null }) : tempGit.fetch(remoteObj.name, { "--prune": null });
|
|
1435
1658
|
await Promise.race([
|
|
@@ -1439,7 +1662,7 @@ async function getBranches(sessionId) {
|
|
|
1439
1662
|
)
|
|
1440
1663
|
]);
|
|
1441
1664
|
}
|
|
1442
|
-
} catch
|
|
1665
|
+
} catch {
|
|
1443
1666
|
}
|
|
1444
1667
|
let branches = await git.branch(["-a"]);
|
|
1445
1668
|
if (branches.all.length === 0) {
|
|
@@ -1461,10 +1684,14 @@ async function getBranches(sessionId) {
|
|
|
1461
1684
|
}
|
|
1462
1685
|
const allBranches = /* @__PURE__ */ new Set();
|
|
1463
1686
|
branches.all.forEach((b) => {
|
|
1687
|
+
if (b.includes("__master_push_temp_")) return;
|
|
1464
1688
|
if (b.startsWith("remotes/")) {
|
|
1465
1689
|
const parts = b.split("/");
|
|
1466
1690
|
if (parts.length > 2) {
|
|
1467
|
-
|
|
1691
|
+
const branchName = parts.slice(2).join("/");
|
|
1692
|
+
if (!branchName.includes("__master_push_temp_")) {
|
|
1693
|
+
allBranches.add(branchName);
|
|
1694
|
+
}
|
|
1468
1695
|
}
|
|
1469
1696
|
} else {
|
|
1470
1697
|
allBranches.add(b);
|
|
@@ -1509,19 +1736,7 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1509
1736
|
const remoteName = remotes[0].name;
|
|
1510
1737
|
await git.addConfig(`branch.${branch}.remote`, remoteName);
|
|
1511
1738
|
await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
|
|
1512
|
-
const tempGit =
|
|
1513
|
-
baseDir: getGitSession(sessionId).repoPath,
|
|
1514
|
-
unsafe: {
|
|
1515
|
-
allowUnsafeEditor: true,
|
|
1516
|
-
allowUnsafeAskPass: true,
|
|
1517
|
-
allowUnsafeCredentialHelper: true
|
|
1518
|
-
}
|
|
1519
|
-
}).env({
|
|
1520
|
-
...process.env,
|
|
1521
|
-
GCM_INTERACTIVE: "false",
|
|
1522
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1523
|
-
GIT_ASKPASS: "echo"
|
|
1524
|
-
});
|
|
1739
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1525
1740
|
const remoteObj = remotes[0];
|
|
1526
1741
|
if (remoteObj && remoteObj.refs.push) {
|
|
1527
1742
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
@@ -1537,29 +1752,31 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1537
1752
|
}
|
|
1538
1753
|
async function addRemote(sessionId, name, url) {
|
|
1539
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
|
+
}
|
|
1540
1762
|
return await git.addRemote(name, url);
|
|
1541
1763
|
}
|
|
1542
1764
|
async function pushAllBranches(sessionId, remote) {
|
|
1543
1765
|
const remotes = await getRemotes(sessionId);
|
|
1544
1766
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1545
|
-
const tempGit =
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1557
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
|
|
1558
|
-
}
|
|
1559
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1560
|
-
} catch (err) {
|
|
1561
|
-
throw err;
|
|
1767
|
+
const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
|
|
1768
|
+
if (remoteObj && remoteObj.refs.push) {
|
|
1769
|
+
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1770
|
+
return await tempGit.raw([
|
|
1771
|
+
"-c",
|
|
1772
|
+
"credential.helper=",
|
|
1773
|
+
"push",
|
|
1774
|
+
"--set-upstream",
|
|
1775
|
+
authUrl,
|
|
1776
|
+
"--all"
|
|
1777
|
+
]);
|
|
1562
1778
|
}
|
|
1779
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", "--set-upstream", remote, "--all"]);
|
|
1563
1780
|
}
|
|
1564
1781
|
async function getCommitHistory(sessionId) {
|
|
1565
1782
|
const { git } = getGitSession(sessionId);
|
|
@@ -1577,17 +1794,27 @@ async function getCommitHistory(sessionId) {
|
|
|
1577
1794
|
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1578
1795
|
]);
|
|
1579
1796
|
}
|
|
1580
|
-
} catch
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
try {
|
|
1800
|
+
const log = await git.log(["--all", "--stat", "--root", "--cc"]);
|
|
1801
|
+
return JSON.parse(JSON.stringify(log));
|
|
1802
|
+
} catch {
|
|
1803
|
+
try {
|
|
1804
|
+
const log = await git.log(["--all", "--stat", "--root"]);
|
|
1805
|
+
return JSON.parse(JSON.stringify(log));
|
|
1806
|
+
} catch {
|
|
1807
|
+
const log = await git.log(["--all", "--name-status"]);
|
|
1808
|
+
return JSON.parse(JSON.stringify(log));
|
|
1809
|
+
}
|
|
1581
1810
|
}
|
|
1582
|
-
const log = await git.log(["--all", "--name-status"]);
|
|
1583
|
-
return JSON.parse(JSON.stringify(log));
|
|
1584
1811
|
}
|
|
1585
1812
|
async function getDeletedHistory(sessionId) {
|
|
1586
1813
|
const { git } = getGitSession(sessionId);
|
|
1587
1814
|
try {
|
|
1588
1815
|
const log = await git.log(["--all", "--diff-filter=D", "--name-status", "-n", "20"]);
|
|
1589
1816
|
return JSON.parse(JSON.stringify(log));
|
|
1590
|
-
} catch
|
|
1817
|
+
} catch {
|
|
1591
1818
|
return { all: [] };
|
|
1592
1819
|
}
|
|
1593
1820
|
}
|
|
@@ -1619,7 +1846,7 @@ async function setGitConfig(sessionId, key, value, global = false) {
|
|
|
1619
1846
|
gitInstance = getGitSession(sessionId).git;
|
|
1620
1847
|
} catch (e) {
|
|
1621
1848
|
if (global) {
|
|
1622
|
-
gitInstance =
|
|
1849
|
+
gitInstance = createSafeGit();
|
|
1623
1850
|
} else {
|
|
1624
1851
|
throw e;
|
|
1625
1852
|
}
|
|
@@ -1673,15 +1900,16 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1673
1900
|
const tempId = crypto.randomUUID();
|
|
1674
1901
|
const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
|
|
1675
1902
|
await fs__namespace.mkdir(workspacePath, { recursive: true });
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1903
|
+
try {
|
|
1904
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", workspacePath]);
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
const git = createSafeGit(workspacePath, sessionId);
|
|
1680
1908
|
await git.init();
|
|
1681
1909
|
await setupDefaultGitAttributes(workspacePath);
|
|
1682
1910
|
let authUrl = remoteUrl;
|
|
1683
1911
|
if (remoteUrl.includes("github.com")) {
|
|
1684
|
-
|
|
1912
|
+
const cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
|
|
1685
1913
|
if (cleanUrl.startsWith("https://github.com/")) {
|
|
1686
1914
|
authUrl = cleanUrl.replace(
|
|
1687
1915
|
"https://github.com/",
|
|
@@ -1699,9 +1927,13 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1699
1927
|
await git.addConfig("user.name", "gitelectron");
|
|
1700
1928
|
await git.addConfig("user.email", "noreply@gitelectron.local");
|
|
1701
1929
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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
|
+
}
|
|
1705
1937
|
const branchToFetch = baseBranch || branch;
|
|
1706
1938
|
try {
|
|
1707
1939
|
await git.fetch(["--depth", "1", "origin", branchToFetch]);
|
|
@@ -1729,7 +1961,7 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1729
1961
|
await fs__namespace.copyFile(source, path__namespace.join(destDirFull, path__namespace.basename(source)));
|
|
1730
1962
|
}
|
|
1731
1963
|
}
|
|
1732
|
-
const git =
|
|
1964
|
+
const git = createSafeGit(workspacePath);
|
|
1733
1965
|
const status = await git.status();
|
|
1734
1966
|
return {
|
|
1735
1967
|
modified: status.modified,
|
|
@@ -1737,28 +1969,18 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1737
1969
|
};
|
|
1738
1970
|
}
|
|
1739
1971
|
async function getUniversalDiff(workspacePath, filePath) {
|
|
1740
|
-
const git =
|
|
1741
|
-
baseDir: workspacePath,
|
|
1742
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1743
|
-
});
|
|
1972
|
+
const git = createSafeGit(workspacePath);
|
|
1744
1973
|
try {
|
|
1745
1974
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1746
1975
|
return diff;
|
|
1747
|
-
} catch
|
|
1976
|
+
} catch {
|
|
1748
1977
|
return "";
|
|
1749
1978
|
}
|
|
1750
1979
|
}
|
|
1751
1980
|
async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
1752
|
-
const git =
|
|
1753
|
-
baseDir: workspacePath,
|
|
1754
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1755
|
-
}).env({
|
|
1756
|
-
...process.env,
|
|
1757
|
-
GCM_INTERACTIVE: "false",
|
|
1758
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1759
|
-
GIT_ASKPASS: "echo"
|
|
1760
|
-
});
|
|
1981
|
+
const git = createSafeGit(workspacePath);
|
|
1761
1982
|
try {
|
|
1983
|
+
await ensureAiWeightsUseLfs(git);
|
|
1762
1984
|
await git.raw([
|
|
1763
1985
|
"-c",
|
|
1764
1986
|
"core.safecrlf=false",
|
|
@@ -1777,12 +1999,12 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1777
1999
|
}
|
|
1778
2000
|
await git.commit(message);
|
|
1779
2001
|
try {
|
|
1780
|
-
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
2002
|
+
await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
|
|
1781
2003
|
} catch (err) {
|
|
1782
2004
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1783
2005
|
console.log("Universal push rejected. Auto-pulling with rebase...");
|
|
1784
|
-
await git.pull("origin", branch, { "--rebase":
|
|
1785
|
-
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
2006
|
+
await git.pull("origin", branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
2007
|
+
await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
|
|
1786
2008
|
return;
|
|
1787
2009
|
}
|
|
1788
2010
|
throw err;
|
|
@@ -1843,7 +2065,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1843
2065
|
await existingGit.raw(["-c", "credential.helper=", "fetch", "--progress", "origin"]);
|
|
1844
2066
|
await new Promise((resolve) => {
|
|
1845
2067
|
const child = child_process.spawn(
|
|
1846
|
-
|
|
2068
|
+
getGitBinary(),
|
|
1847
2069
|
[
|
|
1848
2070
|
"-c",
|
|
1849
2071
|
"core.longpaths=true",
|
|
@@ -1901,7 +2123,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1901
2123
|
cwd = path__namespace.dirname(destPath);
|
|
1902
2124
|
}
|
|
1903
2125
|
await new Promise((resolve, reject) => {
|
|
1904
|
-
const child = child_process.spawn(
|
|
2126
|
+
const child = child_process.spawn(getGitBinary(), cloneArgs, {
|
|
1905
2127
|
cwd,
|
|
1906
2128
|
env: {
|
|
1907
2129
|
...process.env,
|
|
@@ -1994,13 +2216,14 @@ async function removeGitMetadata(_sessionId, repoPath) {
|
|
|
1994
2216
|
}
|
|
1995
2217
|
async function getDirectoryGitInfo(dir) {
|
|
1996
2218
|
try {
|
|
1997
|
-
const
|
|
2219
|
+
const rootPath = await findGitRoot(dir) || dir;
|
|
2220
|
+
const isRepo = await isGitRepo(rootPath);
|
|
1998
2221
|
if (!isRepo) return { isRepo: false, remotes: [] };
|
|
1999
|
-
const tempGit = createSafeGit(
|
|
2000
|
-
let
|
|
2222
|
+
const tempGit = createSafeGit(rootPath);
|
|
2223
|
+
let detectedRoot = void 0;
|
|
2001
2224
|
try {
|
|
2002
|
-
|
|
2003
|
-
if (
|
|
2225
|
+
detectedRoot = (await tempGit.revparse(["--show-toplevel"])).trim();
|
|
2226
|
+
if (detectedRoot) detectedRoot = path__namespace.normalize(detectedRoot);
|
|
2004
2227
|
} catch {
|
|
2005
2228
|
}
|
|
2006
2229
|
const remotes = await tempGit.getRemotes(true);
|
|
@@ -2016,7 +2239,7 @@ async function getDirectoryGitInfo(dir) {
|
|
|
2016
2239
|
}
|
|
2017
2240
|
return {
|
|
2018
2241
|
isRepo: true,
|
|
2019
|
-
rootPath:
|
|
2242
|
+
rootPath: detectedRoot || rootPath,
|
|
2020
2243
|
remotes: JSON.parse(JSON.stringify(remotes)),
|
|
2021
2244
|
repoName,
|
|
2022
2245
|
remoteUrl
|
|
@@ -2064,14 +2287,65 @@ async function scanForRepoFolder(repoName, candidateBaseDirs) {
|
|
|
2064
2287
|
async function removeGitLock(sessionId) {
|
|
2065
2288
|
const { repoPath } = getGitSession(sessionId);
|
|
2066
2289
|
if (!repoPath) return false;
|
|
2067
|
-
const lockPath =
|
|
2290
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
2068
2291
|
try {
|
|
2069
|
-
await
|
|
2292
|
+
await fs__namespace.unlink(lockPath);
|
|
2070
2293
|
return true;
|
|
2071
|
-
} catch
|
|
2294
|
+
} catch {
|
|
2072
2295
|
return false;
|
|
2073
2296
|
}
|
|
2074
2297
|
}
|
|
2298
|
+
async function ensureAiWeightsUseLfs(git) {
|
|
2299
|
+
try {
|
|
2300
|
+
const aiExtensions = [
|
|
2301
|
+
"*.pt",
|
|
2302
|
+
"*.pth",
|
|
2303
|
+
"*.ckpt",
|
|
2304
|
+
"*.safetensors",
|
|
2305
|
+
"*.bin",
|
|
2306
|
+
"*.onnx",
|
|
2307
|
+
"*.h5",
|
|
2308
|
+
"*.tflite",
|
|
2309
|
+
"*.pb",
|
|
2310
|
+
"*.gguf"
|
|
2311
|
+
];
|
|
2312
|
+
await git.raw(["lfs", "install"]);
|
|
2313
|
+
for (const ext of aiExtensions) {
|
|
2314
|
+
await git.raw(["lfs", "track", ext]);
|
|
2315
|
+
}
|
|
2316
|
+
} catch (e) {
|
|
2317
|
+
console.warn("Failed to configure LFS for AI weights:", e);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const GITHUB_MODELS_CONFIG = {
|
|
2321
|
+
baseURL: "https://models.inference.ai.azure.com",
|
|
2322
|
+
defaultModel: "gpt-4o-mini",
|
|
2323
|
+
fallbackModel: "meta-llama-3.1-8b-instruct"
|
|
2324
|
+
};
|
|
2325
|
+
const CIPHER_ALGO = "aes-256-cbc";
|
|
2326
|
+
const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
|
|
2327
|
+
const CIPHER_SALT = "salt_gitelectron";
|
|
2328
|
+
const ENCRYPTED_BACKUP_PAYLOAD = "1aa43686251fc27545934564c92ebcd9:acac927d94c7479c0df45b9245f61f47bd4184fca2323e6ce971bc5c74dffaac925caa81788ce81374a05c7072301ee2eac10531be99a850453ff3fd23ea21cd11a8fa95d07d19fe1a8abcdd9a282b06d3e391a621137027c03765f65e977ed432c00e0e9627b2dad77ca708273eff14eac1bbfd01085d4b645a271167bae46edec2a09491b877fba73c3d29dc2e0e2120785347c9189db6bc9892a6018b6dea7ac064b991e9687e2df56e983743985f";
|
|
2329
|
+
function getBackupApiKeys() {
|
|
2330
|
+
try {
|
|
2331
|
+
const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
|
|
2332
|
+
if (!ivHex || !dataHex) return [];
|
|
2333
|
+
const key = crypto.scryptSync(CIPHER_SECRET, CIPHER_SALT, 32);
|
|
2334
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
2335
|
+
const decipher = crypto.createDecipheriv(CIPHER_ALGO, key, iv);
|
|
2336
|
+
let decrypted = decipher.update(dataHex, "hex", "utf8");
|
|
2337
|
+
decrypted += decipher.final("utf8");
|
|
2338
|
+
return decrypted.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
2339
|
+
} catch (err) {
|
|
2340
|
+
console.warn("[AI Setup] Failed to decrypt backup keys in memory:", err);
|
|
2341
|
+
return [];
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function isValidGitHubToken(token) {
|
|
2345
|
+
if (!token || typeof token !== "string") return false;
|
|
2346
|
+
const trimmed = token.trim();
|
|
2347
|
+
return trimmed.startsWith("gho_") || trimmed.startsWith("ghp_") || trimmed.startsWith("github_pat_") || trimmed.length >= 30;
|
|
2348
|
+
}
|
|
2075
2349
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
2076
2350
|
The user has encountered a Git error during a push, pull, or merge operation and you are executing as a background process to silently fix it.
|
|
2077
2351
|
You will receive the Git Status and the Raw Error Message.
|
|
@@ -2089,15 +2363,23 @@ Return the response strictly as a JSON object with this shape, and absolutely no
|
|
|
2089
2363
|
"explanation": "Internal status message...",
|
|
2090
2364
|
"commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
|
|
2091
2365
|
}`;
|
|
2092
|
-
async function
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
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;
|
|
2098
2377
|
}
|
|
2099
2378
|
}
|
|
2100
|
-
|
|
2379
|
+
throw lastErr || new Error("All Gemini model fallbacks failed");
|
|
2380
|
+
}
|
|
2381
|
+
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2382
|
+
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2101
2383
|
const prompt = `
|
|
2102
2384
|
GIT STATUS:
|
|
2103
2385
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2140,9 +2422,7 @@ ${errorMsg}
|
|
|
2140
2422
|
try {
|
|
2141
2423
|
if (provider === "gemini") {
|
|
2142
2424
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2143
|
-
|
|
2144
|
-
const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2145
|
-
responseText = result.response.text();
|
|
2425
|
+
responseText = await generateWithGemini(genAI, [{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2146
2426
|
} else if (provider === "deepseek") {
|
|
2147
2427
|
const openai = new OpenAI({
|
|
2148
2428
|
apiKey,
|
|
@@ -2245,10 +2525,7 @@ Guidelines:
|
|
|
2245
2525
|
- If there are no conflicted files (e.g. just a standard rejection that a pull will fix), omit "resolvedCode".
|
|
2246
2526
|
- Return ONLY valid JSON. No markdown code block wrappers.`;
|
|
2247
2527
|
async function analyzePushErrorForAutoSync(provider, apiKeyInput, errorMsg, gitStatus, _repoPath, conflictedFilesContext, githubToken) {
|
|
2248
|
-
|
|
2249
|
-
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
2250
|
-
}
|
|
2251
|
-
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
2528
|
+
const keys = getBackupApiKeys();
|
|
2252
2529
|
let prompt = `
|
|
2253
2530
|
GIT STATUS:
|
|
2254
2531
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2269,14 +2546,14 @@ ${content}
|
|
|
2269
2546
|
}
|
|
2270
2547
|
}
|
|
2271
2548
|
let lastError = null;
|
|
2272
|
-
if (githubToken) {
|
|
2549
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2273
2550
|
try {
|
|
2274
2551
|
const openai = new OpenAI({
|
|
2275
2552
|
apiKey: githubToken,
|
|
2276
|
-
baseURL:
|
|
2553
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2277
2554
|
});
|
|
2278
2555
|
const response = await openai.chat.completions.create({
|
|
2279
|
-
model:
|
|
2556
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2280
2557
|
messages: [
|
|
2281
2558
|
{ role: "system", content: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2282
2559
|
{ role: "user", content: prompt }
|
|
@@ -2298,12 +2575,10 @@ ${content}
|
|
|
2298
2575
|
try {
|
|
2299
2576
|
if (provider === "gemini") {
|
|
2300
2577
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2301
|
-
|
|
2302
|
-
const result = await model.generateContent([
|
|
2578
|
+
responseText = await generateWithGemini(genAI, [
|
|
2303
2579
|
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2304
2580
|
{ text: prompt }
|
|
2305
2581
|
]);
|
|
2306
|
-
responseText = result.response.text();
|
|
2307
2582
|
}
|
|
2308
2583
|
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
2309
2584
|
const parsed = JSON.parse(responseText);
|
|
@@ -2330,17 +2605,17 @@ If the diff is empty or invalid, return "Auto-sync: update files".`;
|
|
|
2330
2605
|
const prompt = `GIT DIFF:
|
|
2331
2606
|
${truncatedDiff}`;
|
|
2332
2607
|
let keys = "".split(",").map((k) => k.trim()).filter((k) => k);
|
|
2333
|
-
if (!keys.length
|
|
2334
|
-
keys =
|
|
2608
|
+
if (!keys.length) {
|
|
2609
|
+
keys = getBackupApiKeys();
|
|
2335
2610
|
}
|
|
2336
|
-
if (githubToken) {
|
|
2611
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2337
2612
|
try {
|
|
2338
2613
|
const openai = new OpenAI({
|
|
2339
2614
|
apiKey: githubToken,
|
|
2340
|
-
baseURL:
|
|
2615
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2341
2616
|
});
|
|
2342
2617
|
const response = await openai.chat.completions.create({
|
|
2343
|
-
model:
|
|
2618
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2344
2619
|
messages: [
|
|
2345
2620
|
{ role: "system", content: systemPrompt },
|
|
2346
2621
|
{ role: "user", content: prompt }
|
|
@@ -2356,9 +2631,8 @@ ${truncatedDiff}`;
|
|
|
2356
2631
|
try {
|
|
2357
2632
|
if (provider === "gemini") {
|
|
2358
2633
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2359
|
-
const
|
|
2360
|
-
|
|
2361
|
-
return result.response.text().trim();
|
|
2634
|
+
const text = await generateWithGemini(genAI, [{ text: systemPrompt }, { text: prompt }]);
|
|
2635
|
+
return text.trim();
|
|
2362
2636
|
}
|
|
2363
2637
|
} catch (e) {
|
|
2364
2638
|
console.warn(`[AI Service] Provider ${provider} commit gen failed: ${e.message}`);
|
|
@@ -2403,6 +2677,22 @@ let globalOnResolveComplete = null;
|
|
|
2403
2677
|
let globalOnMassiveProgress = null;
|
|
2404
2678
|
const DEBOUNCE_MS = 3e3;
|
|
2405
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
|
+
}
|
|
2406
2696
|
function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete, onMassiveProgress) {
|
|
2407
2697
|
isAutoSyncEnabled = enabled;
|
|
2408
2698
|
globalOnLog = onLog;
|
|
@@ -2446,9 +2736,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2446
2736
|
}, 25e3);
|
|
2447
2737
|
currentWatcher = chokidar.watch(repoPath, {
|
|
2448
2738
|
ignored: [
|
|
2449
|
-
/(^|[
|
|
2739
|
+
/(^|[/\\])\.git([/\\]|$)/,
|
|
2450
2740
|
// strictly ignore .git directory
|
|
2451
|
-
/(^|[
|
|
2741
|
+
/(^|[/\\])\.DS_Store$/,
|
|
2452
2742
|
// strictly ignore .DS_Store files
|
|
2453
2743
|
/node_modules/,
|
|
2454
2744
|
/dist/,
|
|
@@ -2457,7 +2747,11 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2457
2747
|
// ignore Office temp lock files (~$README.md, etc.)
|
|
2458
2748
|
],
|
|
2459
2749
|
persistent: true,
|
|
2460
|
-
ignoreInitial: true
|
|
2750
|
+
ignoreInitial: true,
|
|
2751
|
+
awaitWriteFinish: {
|
|
2752
|
+
stabilityThreshold: 2e3,
|
|
2753
|
+
pollInterval: 100
|
|
2754
|
+
}
|
|
2461
2755
|
});
|
|
2462
2756
|
currentWatcher.on("all", (_event, filePath) => {
|
|
2463
2757
|
if (!isAutoSyncEnabled || isResolving) return;
|
|
@@ -2514,7 +2808,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2514
2808
|
return;
|
|
2515
2809
|
}
|
|
2516
2810
|
const branch = finalStatus.current || "main";
|
|
2517
|
-
onLog("Generating
|
|
2811
|
+
onLog("🤖 Generating commit summary via GitHub Models...");
|
|
2518
2812
|
let commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
|
|
2519
2813
|
try {
|
|
2520
2814
|
const diffStr = await getFileDiff(sessionId, "", true);
|
|
@@ -2532,7 +2826,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2532
2826
|
await commitChanges(sessionId, commitMsg);
|
|
2533
2827
|
onLog(`Pushing to ${branch}...`);
|
|
2534
2828
|
await pushToRemote(sessionId, "origin", branch);
|
|
2535
|
-
onLog(`✅
|
|
2829
|
+
onLog(`✅ Pushed to origin/${branch} [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`);
|
|
2536
2830
|
} catch (error) {
|
|
2537
2831
|
onLog(`❌ Auto-sync failed: ${error.message}`);
|
|
2538
2832
|
if (globalOnErrorApproval && !isResolving) {
|
|
@@ -2552,10 +2846,10 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2552
2846
|
}
|
|
2553
2847
|
if (errorMsg.includes("index.lock")) {
|
|
2554
2848
|
onLog("Git lock detected. Automatically clearing zombie lock file...");
|
|
2555
|
-
const lockPath =
|
|
2849
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
2556
2850
|
try {
|
|
2557
|
-
await
|
|
2558
|
-
} catch
|
|
2851
|
+
await fs__namespace.unlink(lockPath);
|
|
2852
|
+
} catch {
|
|
2559
2853
|
}
|
|
2560
2854
|
isResolving = false;
|
|
2561
2855
|
return;
|
|
@@ -2564,7 +2858,11 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2564
2858
|
onLog("Pulling remote changes with rebase...");
|
|
2565
2859
|
const status2 = await getStatus(sessionId);
|
|
2566
2860
|
const currentBranch = status2.current || "main";
|
|
2567
|
-
await git.pull("origin", currentBranch, {
|
|
2861
|
+
await git.pull("origin", currentBranch, {
|
|
2862
|
+
"--rebase": "false",
|
|
2863
|
+
"--autostash": null,
|
|
2864
|
+
"-X": "ours"
|
|
2865
|
+
});
|
|
2568
2866
|
onLog("Pull successful, attempting push again...");
|
|
2569
2867
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2570
2868
|
onLog("✅ Error resolved and push resumed!");
|
|
@@ -2593,7 +2891,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2593
2891
|
try {
|
|
2594
2892
|
const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
|
|
2595
2893
|
conflictedFilesContext[f] = content;
|
|
2596
|
-
} catch
|
|
2894
|
+
} catch {
|
|
2597
2895
|
}
|
|
2598
2896
|
}
|
|
2599
2897
|
try {
|
|
@@ -2657,7 +2955,7 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2657
2955
|
}
|
|
2658
2956
|
try {
|
|
2659
2957
|
await git.rebase({ "--continue": null });
|
|
2660
|
-
} catch
|
|
2958
|
+
} catch {
|
|
2661
2959
|
await git.commit("Auto-resolved merge conflict by AI");
|
|
2662
2960
|
}
|
|
2663
2961
|
const status = await getStatus(sessionId);
|
|
@@ -2677,6 +2975,53 @@ function cancelAiFix() {
|
|
|
2677
2975
|
isResolving = false;
|
|
2678
2976
|
}
|
|
2679
2977
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
2978
|
+
if (process.platform === "darwin") {
|
|
2979
|
+
const macPaths = [
|
|
2980
|
+
"/opt/homebrew/bin",
|
|
2981
|
+
"/opt/homebrew/sbin",
|
|
2982
|
+
"/usr/local/bin",
|
|
2983
|
+
"/usr/local/sbin",
|
|
2984
|
+
"/usr/bin",
|
|
2985
|
+
"/bin",
|
|
2986
|
+
"/usr/sbin",
|
|
2987
|
+
"/sbin"
|
|
2988
|
+
];
|
|
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
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
process.env.PATH = pathParts.join(";");
|
|
3020
|
+
}
|
|
3021
|
+
if (process.platform === "win32") {
|
|
3022
|
+
electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
|
3023
|
+
electron.app.commandLine.appendSwitch("disable-color-correct-rendering");
|
|
3024
|
+
}
|
|
2680
3025
|
function createWindow(initialPath) {
|
|
2681
3026
|
const mainWindow = new electron.BrowserWindow({
|
|
2682
3027
|
width: 1100,
|
|
@@ -2700,6 +3045,7 @@ function createWindow(initialPath) {
|
|
|
2700
3045
|
mainWindow.on("closed", () => {
|
|
2701
3046
|
cleanupSession(sessionId);
|
|
2702
3047
|
cleanupGitSession(sessionId);
|
|
3048
|
+
stopAutoSync();
|
|
2703
3049
|
});
|
|
2704
3050
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
|
2705
3051
|
electron.shell.openExternal(details.url);
|
|
@@ -2745,15 +3091,67 @@ electron.app.on("second-instance", (_event, commandLine, workingDirectory) => {
|
|
|
2745
3091
|
});
|
|
2746
3092
|
if (process.defaultApp) {
|
|
2747
3093
|
if (process.argv.length >= 2) {
|
|
2748
|
-
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [
|
|
2749
|
-
require("path").resolve(process.argv[1])
|
|
2750
|
-
]);
|
|
3094
|
+
electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [path__namespace.resolve(process.argv[1])]);
|
|
2751
3095
|
}
|
|
2752
3096
|
} else {
|
|
2753
3097
|
electron.app.setAsDefaultProtocolClient("gitelectron");
|
|
2754
3098
|
}
|
|
2755
3099
|
electron.app.whenReady().then(async () => {
|
|
2756
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
|
+
}
|
|
2757
3155
|
await loadAccounts();
|
|
2758
3156
|
electron.app.on("browser-window-created", (_, window) => {
|
|
2759
3157
|
utils.optimizer.watchWindowShortcuts(window);
|
|
@@ -2786,25 +3184,33 @@ electron.app.whenReady().then(async () => {
|
|
|
2786
3184
|
if (result.canceled || result.filePaths.length === 0) {
|
|
2787
3185
|
return null;
|
|
2788
3186
|
}
|
|
2789
|
-
const
|
|
2790
|
-
const
|
|
3187
|
+
const chosenPath = result.filePaths[0];
|
|
3188
|
+
const rootPath = await findGitRoot(chosenPath) || chosenPath;
|
|
3189
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3190
|
+
recordSessionPath(sessionId, rootPath);
|
|
2791
3191
|
if (isRepo) {
|
|
2792
|
-
initGit(sessionId,
|
|
3192
|
+
initGit(sessionId, rootPath);
|
|
2793
3193
|
}
|
|
2794
|
-
await saveLastWorkspace(
|
|
2795
|
-
return { path:
|
|
3194
|
+
await saveLastWorkspace(rootPath);
|
|
3195
|
+
return { path: rootPath, isRepo };
|
|
2796
3196
|
});
|
|
2797
|
-
electron.ipcMain.handle("set-repo-path", async (event,
|
|
3197
|
+
electron.ipcMain.handle("set-repo-path", async (event, inputPath) => {
|
|
2798
3198
|
const sessionId = event.sender.id;
|
|
2799
|
-
|
|
3199
|
+
if (!inputPath) return { path: "", isRepo: false };
|
|
3200
|
+
const rootPath = await findGitRoot(inputPath) || inputPath;
|
|
3201
|
+
const isRepo = await isGitRepo(rootPath);
|
|
3202
|
+
recordSessionPath(sessionId, rootPath);
|
|
2800
3203
|
if (isRepo) {
|
|
2801
|
-
initGit(sessionId,
|
|
3204
|
+
initGit(sessionId, rootPath);
|
|
2802
3205
|
}
|
|
2803
|
-
await saveLastWorkspace(
|
|
2804
|
-
return { path:
|
|
3206
|
+
await saveLastWorkspace(rootPath);
|
|
3207
|
+
return { path: rootPath, isRepo };
|
|
2805
3208
|
});
|
|
2806
3209
|
electron.ipcMain.handle("init-repo", async (event, path2) => {
|
|
2807
|
-
|
|
3210
|
+
recordSessionPath(event.sender.id, path2);
|
|
3211
|
+
const result = await initializeRepo(event.sender.id, path2);
|
|
3212
|
+
await saveLastWorkspace(path2);
|
|
3213
|
+
return result;
|
|
2808
3214
|
});
|
|
2809
3215
|
electron.ipcMain.handle("get-status", async (event) => {
|
|
2810
3216
|
return await getStatus(event.sender.id);
|
|
@@ -3031,6 +3437,20 @@ electron.app.whenReady().then(async () => {
|
|
|
3031
3437
|
electron.ipcMain.handle("remove-git-lock", async (event) => {
|
|
3032
3438
|
return await removeGitLock(event.sender.id);
|
|
3033
3439
|
});
|
|
3440
|
+
electron.ipcMain.handle("clear-app-cache", async (event) => {
|
|
3441
|
+
try {
|
|
3442
|
+
if (electron.session.defaultSession) {
|
|
3443
|
+
await electron.session.defaultSession.clearCache();
|
|
3444
|
+
}
|
|
3445
|
+
try {
|
|
3446
|
+
await removeGitLock(event.sender.id);
|
|
3447
|
+
} catch {
|
|
3448
|
+
}
|
|
3449
|
+
return { success: true };
|
|
3450
|
+
} catch (e) {
|
|
3451
|
+
return { success: false, error: e.message };
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3034
3454
|
electron.ipcMain.handle("check-path-exists", async (_event, targetPath) => {
|
|
3035
3455
|
if (!targetPath) return false;
|
|
3036
3456
|
try {
|
|
@@ -3092,9 +3512,8 @@ electron.app.whenReady().then(async () => {
|
|
|
3092
3512
|
});
|
|
3093
3513
|
electron.ipcMain.handle("check-updates", async () => {
|
|
3094
3514
|
try {
|
|
3095
|
-
const https = require("https");
|
|
3096
3515
|
return new Promise((resolve) => {
|
|
3097
|
-
|
|
3516
|
+
https__namespace.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
|
|
3098
3517
|
let data = "";
|
|
3099
3518
|
res.on("data", (chunk) => {
|
|
3100
3519
|
data += chunk;
|
|
@@ -3103,31 +3522,39 @@ electron.app.whenReady().then(async () => {
|
|
|
3103
3522
|
try {
|
|
3104
3523
|
const json = JSON.parse(data);
|
|
3105
3524
|
resolve(json.version || null);
|
|
3106
|
-
} catch
|
|
3525
|
+
} catch {
|
|
3107
3526
|
resolve(null);
|
|
3108
3527
|
}
|
|
3109
3528
|
});
|
|
3110
3529
|
}).on("error", () => resolve(null));
|
|
3111
3530
|
});
|
|
3112
|
-
} catch
|
|
3531
|
+
} catch {
|
|
3113
3532
|
return null;
|
|
3114
3533
|
}
|
|
3115
3534
|
});
|
|
3116
3535
|
electron.ipcMain.handle("install-update", async () => {
|
|
3117
3536
|
return new Promise((resolve) => {
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3537
|
+
if (process.platform === "win32") {
|
|
3538
|
+
const cmdArgs = [
|
|
3539
|
+
"/c",
|
|
3540
|
+
"start",
|
|
3541
|
+
"cmd.exe",
|
|
3542
|
+
"/c",
|
|
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"
|
|
3544
|
+
];
|
|
3545
|
+
const child = child_process.spawn("cmd.exe", cmdArgs, {
|
|
3546
|
+
detached: true,
|
|
3547
|
+
stdio: "ignore"
|
|
3548
|
+
});
|
|
3549
|
+
child.unref();
|
|
3550
|
+
} else {
|
|
3551
|
+
const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
|
|
3552
|
+
const child = child_process.spawn("sh", ["-c", shScript], {
|
|
3553
|
+
detached: true,
|
|
3554
|
+
stdio: "ignore"
|
|
3555
|
+
});
|
|
3556
|
+
child.unref();
|
|
3557
|
+
}
|
|
3131
3558
|
resolve(true);
|
|
3132
3559
|
setTimeout(() => {
|
|
3133
3560
|
electron.app.exit(0);
|