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/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 child_process = require("child_process");
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) return { status: "Not Authenticated", username: null };
115
- return { status: "Verified", username: activeAccount.username };
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: "git",
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 (e) {
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: "git",
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 (e) {
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
- const session = gitSessions.get(sessionId);
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 (e) {
823
+ } catch {
705
824
  }
706
- let newLines = [];
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 (e) {
835
+ } catch {
717
836
  }
718
837
  }
719
- async function isGitRepo(dir) {
720
- let hasGitFolder = false;
838
+ async function findGitRoot(dir) {
839
+ if (!dir) return null;
840
+ const cleanDir = path__namespace.normalize(dir.trim());
721
841
  try {
722
- await fs__namespace.access(path__namespace.join(dir, ".git"));
723
- hasGitFolder = true;
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 = simpleGit.simpleGit(dir).env({
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) return true;
736
- if (hasGitFolder) {
737
- await simpleGit.simpleGit().raw(["config", "--global", "--add", "safe.directory", dir]);
738
- return await tempGit.checkIsRepo();
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
- if (hasGitFolder) {
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 simpleGit.simpleGit().raw(["config", "--global", "--add", "safe.directory", dir]);
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
- return false;
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
- const tempGit = simpleGit.simpleGit({
762
- baseDir: dir,
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
- await tempGit.init();
770
- const branches = await tempGit.branchLocal();
771
- if (branches.all.length === 0) {
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
- await setupDefaultGitAttributes(dir);
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
- return false;
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
- return new Promise((resolve, reject) => {
794
- const child = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
795
- let count = 0;
796
- let isMassive = false;
797
- child.stdout.on("data", (chunk) => {
798
- for (let i = 0; i < chunk.length; ++i) {
799
- if (chunk[i] === 10) {
800
- count++;
801
- if (count > 1e4) {
802
- isMassive = true;
803
- child.kill();
804
- break;
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
- child.on("close", async () => {
810
- if (isMassive) {
811
- resolve({
812
- not_added: [],
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 (e) {
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 (e) {
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("git", ["status", "--porcelain"], { cwd: repoPath });
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("git", ["status", "--porcelain", "-z"], { cwd: repoPath });
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 (e) {
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 (e) {
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 (e) {
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": null, "--autostash": null });
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 = simpleGit.simpleGit({
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": null, "--autostash": null };
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
- await git.raw(["add", "-A", "-f"]);
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 - Including all files");
1427
+ await git.commit("Master Force Push - Sync changes");
1192
1428
  } catch (e) {
1193
1429
  if (!e.message.includes("nothing to commit")) {
1194
- throw e;
1430
+ console.warn("Commit note:", e.message);
1195
1431
  }
1196
1432
  }
1197
- const tempGit = simpleGit.simpleGit({
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.push) {
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("git", pullArgs, {
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
- "git",
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 = simpleGit.simpleGit({
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 (e) {
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
- allBranches.add(parts.slice(2).join("/"));
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 = simpleGit.simpleGit({
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 = simpleGit.simpleGit({
1546
- baseDir: getGitSession(sessionId).repoPath,
1547
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1548
- }).env({
1549
- ...process.env,
1550
- GCM_INTERACTIVE: "false",
1551
- GIT_TERMINAL_PROMPT: "0",
1552
- GIT_ASKPASS: "echo"
1553
- });
1554
- try {
1555
- if (remoteObj && remoteObj.refs.push) {
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 (e) {
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 (e) {
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 = simpleGit.simpleGit();
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
- const git = simpleGit.simpleGit({
1677
- baseDir: workspacePath,
1678
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
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
- let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
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
- await git.raw(["sparse-checkout", "init", "--cone"]);
1703
- const sparsePath = destinationPath === "/" || destinationPath === "" ? "" : destinationPath;
1704
- await git.raw(["sparse-checkout", "set", sparsePath]);
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 = simpleGit.simpleGit(workspacePath);
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 = simpleGit.simpleGit({
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 (e) {
1976
+ } catch {
1748
1977
  return "";
1749
1978
  }
1750
1979
  }
1751
1980
  async function commitUniversalWorkspace(workspacePath, message, branch) {
1752
- const git = simpleGit.simpleGit({
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": null, "--autostash": null });
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
- "git",
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("git", cloneArgs, {
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 isRepo = await isGitRepo(dir);
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(dir);
2000
- let rootPath = void 0;
2222
+ const tempGit = createSafeGit(rootPath);
2223
+ let detectedRoot = void 0;
2001
2224
  try {
2002
- rootPath = (await tempGit.revparse(["--show-toplevel"])).trim();
2003
- if (rootPath) rootPath = path__namespace.normalize(rootPath);
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: rootPath || dir,
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 = require("path").join(repoPath, ".git", "index.lock");
2290
+ const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
2068
2291
  try {
2069
- await require("fs/promises").unlink(lockPath);
2292
+ await fs__namespace.unlink(lockPath);
2070
2293
  return true;
2071
- } catch (e) {
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 analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
2093
- if (!apiKeyInput) {
2094
- if (provider === "gemini") {
2095
- apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
2096
- } else {
2097
- apiKeyInput = "deepseek-hardcoded-key-if-applicable";
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
- const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
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
- const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
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
- if (!apiKeyInput && provider === "gemini") {
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: "https://models.inference.ai.azure.com"
2553
+ baseURL: GITHUB_MODELS_CONFIG.baseURL
2277
2554
  });
2278
2555
  const response = await openai.chat.completions.create({
2279
- model: "gpt-4o-mini",
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
- const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
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 && provider === "gemini") {
2334
- keys = ["AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw"];
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: "https://models.inference.ai.azure.com"
2615
+ baseURL: GITHUB_MODELS_CONFIG.baseURL
2341
2616
  });
2342
2617
  const response = await openai.chat.completions.create({
2343
- model: "gpt-4o-mini",
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 model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
2360
- const result = await model.generateContent([{ text: systemPrompt }, { text: prompt }]);
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
- /(^|[\/\\])\.git([\/\\]|$)/,
2739
+ /(^|[/\\])\.git([/\\]|$)/,
2450
2740
  // strictly ignore .git directory
2451
- /(^|[\/\\])\.DS_Store$/,
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 AI commit message...");
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(`✅ Successfully pushed auto-sync commit!`);
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 = require("path").join(repoPath, ".git", "index.lock");
2849
+ const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
2556
2850
  try {
2557
- await require("fs/promises").unlink(lockPath);
2558
- } catch (e) {
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, { "--rebase": "true", "--autostash": null });
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 (e) {
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 (e) {
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 path2 = result.filePaths[0];
2790
- const isRepo = await isGitRepo(path2);
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, path2);
3192
+ initGit(sessionId, rootPath);
2793
3193
  }
2794
- await saveLastWorkspace(path2);
2795
- return { path: path2, isRepo };
3194
+ await saveLastWorkspace(rootPath);
3195
+ return { path: rootPath, isRepo };
2796
3196
  });
2797
- electron.ipcMain.handle("set-repo-path", async (event, path2) => {
3197
+ electron.ipcMain.handle("set-repo-path", async (event, inputPath) => {
2798
3198
  const sessionId = event.sender.id;
2799
- const isRepo = await isGitRepo(path2);
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, path2);
3204
+ initGit(sessionId, rootPath);
2802
3205
  }
2803
- await saveLastWorkspace(path2);
2804
- return { path: path2, isRepo };
3206
+ await saveLastWorkspace(rootPath);
3207
+ return { path: rootPath, isRepo };
2805
3208
  });
2806
3209
  electron.ipcMain.handle("init-repo", async (event, path2) => {
2807
- return await initializeRepo(event.sender.id, path2);
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
- https.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
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 (e) {
3525
+ } catch {
3107
3526
  resolve(null);
3108
3527
  }
3109
3528
  });
3110
3529
  }).on("error", () => resolve(null));
3111
3530
  });
3112
- } catch (e) {
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
- const { spawn } = require("child_process");
3119
- const cmdArgs = [
3120
- "/c",
3121
- "start",
3122
- "cmd.exe",
3123
- "/c",
3124
- "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"
3125
- ];
3126
- const child = spawn("cmd.exe", cmdArgs, {
3127
- detached: true,
3128
- stdio: "ignore"
3129
- });
3130
- child.unref();
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);