gitxelectron 1.4.1 → 1.4.2

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