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.
- package/README.md +148 -102
- package/package.json +6 -1
- package/src/config.js +64 -2
- package/src/gitCommit.js +53 -0
- package/src/ui/public/assets/index-BPoaF8NL.js +44 -0
- package/src/ui/public/assets/index-C06RP6-q.css +1 -0
- package/src/ui/public/assets/vendor-CKXtUB8u.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +111 -23
- package/src/utils/index.js +87 -3
- package/src/ui/public/assets/index-BKjt1yVw.js +0 -44
- package/src/ui/public/assets/index-th8vfo_E.css +0 -1
- package/src/ui/public/assets/vendor-Ym9LLftt.js +0 -45
package/src/ui/public/index.html
CHANGED
|
@@ -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-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
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-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C06RP6-q.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
package/src/ui/server/index.js
CHANGED
|
@@ -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
|
-
//
|
|
963
|
-
await
|
|
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
|
-
|
|
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 {
|
package/src/utils/index.js
CHANGED
|
@@ -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
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
};
|