zen-gitsync 2.1.29 → 2.2.1

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.
@@ -5,10 +5,10 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Zen-GitSync - Git同步工具</title>
8
- <script type="module" crossorigin src="/assets/index-BKjt1yVw.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vendor-Ym9LLftt.js">
8
+ <script type="module" crossorigin src="/assets/index-BPoaF8NL.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/vendor-CKXtUB8u.js">
10
10
  <link rel="stylesheet" crossorigin href="/assets/vendor-BLnrYq3C.css">
11
- <link rel="stylesheet" crossorigin href="/assets/index-th8vfo_E.css">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-C06RP6-q.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="app"></div>
@@ -2,7 +2,7 @@ import express from 'express';
2
2
  import { createServer } from 'http';
3
3
  import { fileURLToPath } from 'url';
4
4
  import path from 'path';
5
- import { execGitCommand, getCommandHistory, clearCommandHistory, registerSocketIO } from '../../utils/index.js';
5
+ import { execGitCommand, getCommandHistory, clearCommandHistory, registerSocketIO, execGitAddWithLockFilter } from '../../utils/index.js';
6
6
  import open from 'open';
7
7
  import config from '../../config.js';
8
8
  import chalk from 'chalk';
@@ -959,8 +959,8 @@ async function startUIServer(noOpen = false, savePort = false) {
959
959
  // 添加 add 接口
960
960
  app.post('/api/add', async (req, res) => {
961
961
  try {
962
- // 执行 git add . 命令添加所有更改
963
- await execGitCommand('git add .');
962
+ // 使用带锁定文件过滤的 git add
963
+ await execGitAddWithLockFilter();
964
964
  res.json({ success: true });
965
965
  } catch (error) {
966
966
  res.status(500).json({ success: false, error: error.message });
@@ -1757,35 +1757,66 @@ async function startUIServer(noOpen = false, savePort = false) {
1757
1757
  // 创建新的stash
1758
1758
  app.post('/api/stash-save', async (req, res) => {
1759
1759
  try {
1760
- const { message, includeUntracked } = req.body;
1761
-
1762
- // 构建stash命令
1760
+ const { message, includeUntracked, excludeLocked } = req.body;
1761
+
1762
+ if (excludeLocked) {
1763
+ const lockedFiles = await configManager.getLockedFiles();
1764
+ const { stdout: statusStdout } = await execGitCommand('git status --porcelain', { log: false });
1765
+ const changedFiles = statusStdout
1766
+ .split('\n')
1767
+ .filter(line => line.trim())
1768
+ .map(line => {
1769
+ const match = line.match(/^..\s+(.+)$/);
1770
+ if (match) {
1771
+ let filename = match[1];
1772
+ if (filename.startsWith('"') && filename.endsWith('"')) {
1773
+ filename = filename.slice(1, -1).replace(/\\(.)/g, '$1');
1774
+ }
1775
+ return filename;
1776
+ }
1777
+ return null;
1778
+ })
1779
+ .filter(Boolean);
1780
+
1781
+ const path = (await import('path')).default;
1782
+ const filesToStash = changedFiles.filter(file => {
1783
+ const normalizedFile = path.normalize(file);
1784
+ const isLocked = lockedFiles.some(locked => {
1785
+ const normalizedLocked = path.normalize(locked);
1786
+ return normalizedFile === normalizedLocked || normalizedFile.startsWith(normalizedLocked + path.sep);
1787
+ });
1788
+ return !isLocked;
1789
+ });
1790
+
1791
+ if (filesToStash.length === 0) {
1792
+ return res.json({ success: false, message: '所有更改都是锁定文件,无需储藏' });
1793
+ }
1794
+
1795
+ let command = 'git stash push';
1796
+ if (message) command += ` -m "${message}"`;
1797
+ if (includeUntracked) command += ' --include-untracked';
1798
+ const args = filesToStash.map(f => `"${f}"`).join(' ');
1799
+ command += ` -- ${args}`;
1800
+
1801
+ const { stdout } = await execGitCommand(command);
1802
+ if (stdout.includes('No local changes to save')) {
1803
+ return res.json({ success: false, message: '没有本地更改需要保存' });
1804
+ }
1805
+ return res.json({ success: true, message: '成功保存未锁定的工作区更改', output: stdout });
1806
+ }
1807
+
1763
1808
  let command = 'git stash push';
1764
-
1765
- // 添加可选参数
1766
1809
  if (message) {
1767
1810
  command += ` -m "${message}"`;
1768
1811
  }
1769
-
1770
1812
  if (includeUntracked) {
1771
1813
  command += ' --include-untracked';
1772
1814
  }
1773
-
1774
1815
  const { stdout } = await execGitCommand(command);
1775
-
1776
- // 检查是否有任何更改被保存
1777
1816
  if (stdout.includes('No local changes to save')) {
1778
- return res.json({
1779
- success: false,
1780
- message: '没有本地更改需要保存'
1781
- });
1817
+ return res.json({ success: false, message: '没有本地更改需要保存' });
1782
1818
  }
1783
-
1784
- res.json({
1785
- success: true,
1786
- message: '成功保存工作区更改',
1787
- output: stdout
1788
- });
1819
+ res.json({ success: true, message: '成功保存工作区更改', output: stdout });
1789
1820
  } catch (error) {
1790
1821
  console.error('保存stash失败:', error);
1791
1822
  res.status(500).json({ success: false, error: error.message });
@@ -2152,7 +2183,64 @@ async function startUIServer(noOpen = false, savePort = false) {
2152
2183
  console.error(`无法找到可用端口 (尝试范围: ${startPort}-${maxPort-1})`);
2153
2184
  process.exit(1);
2154
2185
  }
2155
-
2186
+
2187
+ // ========== 文件锁定相关 API ==========
2188
+
2189
+ // 获取锁定文件列表
2190
+ app.get('/api/locked-files', async (req, res) => {
2191
+ try {
2192
+ const lockedFiles = await configManager.getLockedFiles();
2193
+ res.json({ success: true, lockedFiles });
2194
+ } catch (error) {
2195
+ res.status(500).json({ success: false, error: error.message });
2196
+ }
2197
+ });
2198
+
2199
+ // 锁定文件
2200
+ app.post('/api/lock-file', async (req, res) => {
2201
+ try {
2202
+ const { filePath } = req.body;
2203
+ if (!filePath) {
2204
+ return res.status(400).json({ success: false, error: '缺少文件路径参数' });
2205
+ }
2206
+
2207
+ const result = await configManager.lockFile(filePath);
2208
+ res.json({ success: true, locked: result });
2209
+ } catch (error) {
2210
+ res.status(500).json({ success: false, error: error.message });
2211
+ }
2212
+ });
2213
+
2214
+ // 解锁文件
2215
+ app.post('/api/unlock-file', async (req, res) => {
2216
+ try {
2217
+ const { filePath } = req.body;
2218
+ if (!filePath) {
2219
+ return res.status(400).json({ success: false, error: '缺少文件路径参数' });
2220
+ }
2221
+
2222
+ const result = await configManager.unlockFile(filePath);
2223
+ res.json({ success: true, unlocked: result });
2224
+ } catch (error) {
2225
+ res.status(500).json({ success: false, error: error.message });
2226
+ }
2227
+ });
2228
+
2229
+ // 检查文件是否锁定
2230
+ app.post('/api/check-file-lock', async (req, res) => {
2231
+ try {
2232
+ const { filePath } = req.body;
2233
+ if (!filePath) {
2234
+ return res.status(400).json({ success: false, error: '缺少文件路径参数' });
2235
+ }
2236
+
2237
+ const isLocked = await configManager.isFileLocked(filePath);
2238
+ res.json({ success: true, isLocked });
2239
+ } catch (error) {
2240
+ res.status(500).json({ success: false, error: error.message });
2241
+ }
2242
+ });
2243
+
2156
2244
  // 添加命令历史的清空API
2157
2245
  app.post('/api/clear-command-history', async (req, res) => {
2158
2246
  try {
@@ -24,6 +24,7 @@ import ora from "ora";
24
24
  import readline from 'readline'
25
25
  import path from 'path'
26
26
  import fs from 'fs/promises'
27
+ import config from '../config.js'
27
28
 
28
29
 
29
30
  const printTableWithHeaderUnderline = (head, content, style) => {
@@ -377,6 +378,12 @@ Options:
377
378
  addResetScript Add "g:reset": "git reset --hard origin/<current-branch>" to package.json scripts
378
379
  ui Launch graphical user interface (v2.0.0)
379
380
 
381
+ File Locking:
382
+ --lock-file=<path> Lock a file to exclude it from commits
383
+ --unlock-file=<path> Unlock a previously locked file
384
+ --list-locked List all currently locked files
385
+ --check-lock=<path> Check if a file is locked
386
+
380
387
  --cmd="your-cmd" Execute custom cmd command (immediately, at a time, or periodically)
381
388
  --cmd-interval=<seconds> Execute custom cmd every N seconds
382
389
  --at="HH:MM" Execute custom cmd at a specific time (today) or --at="YYYY-MM-DD HH:MM:SS"
@@ -395,6 +402,9 @@ Example:
395
402
  g log --n=5 Show the last 5 commits with --log
396
403
  g addScript Add auto commit script to package.json
397
404
  g addResetScript Add reset script to package.json
405
+ g --lock-file=config.json Lock config.json file
406
+ g --unlock-file=config.json Unlock config.json file
407
+ g --list-locked List all locked files
398
408
 
399
409
  Add auto submit in package.json:
400
410
  "scripts": {
@@ -616,6 +626,78 @@ async function execDiff() {
616
626
  }
617
627
  }
618
628
 
629
+ // 执行 git add 但排除锁定的文件
630
+ async function execGitAddWithLockFilter() {
631
+ try {
632
+ // 获取锁定的文件列表
633
+ const lockedFiles = await config.getLockedFiles();
634
+
635
+ if (lockedFiles.length === 0) {
636
+ // 如果没有锁定文件,直接执行 git add .
637
+ await execGitCommand('git add .');
638
+ return;
639
+ }
640
+
641
+ // 获取所有修改的文件
642
+ const statusResult = await execGitCommand('git status --porcelain', {log: false});
643
+ const modifiedFiles = statusResult.stdout
644
+ .split('\n')
645
+ .filter(line => line.trim())
646
+ .map(line => {
647
+ // 解析 git status --porcelain 的输出格式
648
+ // 格式: XY filename 或 XY "filename with spaces"
649
+ const match = line.match(/^..\s+(.+)$/);
650
+ if (match) {
651
+ let filename = match[1];
652
+ // 如果文件名被引号包围,去掉引号
653
+ if (filename.startsWith('"') && filename.endsWith('"')) {
654
+ filename = filename.slice(1, -1);
655
+ // 处理转义字符
656
+ filename = filename.replace(/\\(.)/g, '$1');
657
+ }
658
+ return filename;
659
+ }
660
+ return null;
661
+ })
662
+ .filter(Boolean);
663
+
664
+ // 过滤掉锁定的文件
665
+ const filesToAdd = modifiedFiles.filter(file => {
666
+ const normalizedFile = path.normalize(file);
667
+ const isLocked = lockedFiles.some(lockedFile => {
668
+ const normalizedLocked = path.normalize(lockedFile);
669
+ return normalizedFile === normalizedLocked ||
670
+ normalizedFile.startsWith(normalizedLocked + path.sep);
671
+ });
672
+
673
+ if (isLocked) {
674
+ console.log(chalk.yellow(`🔒 跳过锁定文件: ${file}`));
675
+ return false;
676
+ }
677
+ return true;
678
+ });
679
+
680
+ if (filesToAdd.length === 0) {
681
+ console.log(chalk.blue('📝 所有修改的文件都被锁定,没有文件需要添加'));
682
+ return;
683
+ }
684
+
685
+ // 逐个添加未锁定的文件
686
+ for (const file of filesToAdd) {
687
+ await execGitCommand(`git add "${file}"`, {
688
+ head: `git add ${file}`,
689
+ log: false
690
+ });
691
+ }
692
+
693
+ console.log(chalk.green(`✅ 已添加 ${filesToAdd.length} 个文件到暂存区 (跳过 ${lockedFiles.length} 个锁定文件)`));
694
+
695
+ } catch (error) {
696
+ console.error(chalk.red('执行 git add 时出错:'), error.message);
697
+ throw error;
698
+ }
699
+ }
700
+
619
701
  async function execAddAndCommit({statusOutput, commitMessage, exit}) {
620
702
  // 检查 -m 参数(提交信息)
621
703
  const commitMessageArg = process.argv.find(arg => arg.startsWith('-m'));
@@ -652,9 +734,10 @@ async function execAddAndCommit({statusOutput, commitMessage, exit}) {
652
734
  commitMessage = await question('请输入提交信息:') || commitMessage;
653
735
  }
654
736
 
655
- statusOutput.includes('(use "git add') && await execGitCommand('git add .')
656
- // 强制添加所有变更
657
- // await execGitCommand('git add -A .');
737
+ // 使用带锁定文件过滤的 git add
738
+ if (statusOutput.includes('(use "git add')) {
739
+ await execGitAddWithLockFilter();
740
+ }
658
741
 
659
742
  // 提交前二次校验
660
743
  const checkStatus = await execGitCommand('git status --porcelain', {log: false});
@@ -757,5 +840,6 @@ export {
757
840
  getCwd, judgePlatform, showHelp, judgeLog, printGitLog,
758
841
  judgeHelp, exec_exit, judgeUnmerged, delay, formatDuration,
759
842
  exec_push, execPull, judgeRemote, execDiff, execAddAndCommit,
843
+ execGitAddWithLockFilter, // 导出新的 git add 函数
760
844
  addScriptToPackageJson, addResetScriptToPackageJson
761
845
  };