zen-gitsync 1.5.6 → 2.0.3
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 +9 -0
- package/package.json +10 -3
- package/src/config.js +2 -1
- package/src/gitCommit.js +7 -0
- package/src/ui/client/README.md +5 -0
- package/src/ui/client/auto-imports.d.ts +10 -0
- package/src/ui/client/components.d.ts +31 -0
- package/src/ui/client/index.html +13 -0
- package/src/ui/client/package.json +27 -0
- package/src/ui/client/public/favicon.svg +27 -0
- package/src/ui/client/public/logo.svg +27 -0
- package/src/ui/client/public/vite.svg +1 -0
- package/src/ui/client/src/App.vue +539 -0
- package/src/ui/client/src/assets/logo.svg +27 -0
- package/src/ui/client/src/components/CommitForm.vue +904 -0
- package/src/ui/client/src/components/GitStatus.vue +799 -0
- package/src/ui/client/src/components/LogList.vue +270 -0
- package/src/ui/client/src/main.ts +4 -0
- package/src/ui/client/src/vite-env.d.ts +1 -0
- package/src/ui/client/stats.html +4949 -0
- package/src/ui/client/tsconfig.app.json +14 -0
- package/src/ui/client/tsconfig.json +7 -0
- package/src/ui/client/tsconfig.node.json +24 -0
- package/src/ui/client/vite.config.ts +48 -0
- package/src/ui/public/assets/index-BHmYZROy.css +1 -0
- package/src/ui/public/assets/index-kfMX1bxz.js +9 -0
- package/src/ui/public/assets/vendor-Dp0FkvMe.css +1 -0
- package/src/ui/public/assets/vendor-DxvF30ca.js +41 -0
- package/src/ui/public/favicon.svg +27 -0
- package/src/ui/public/index.html +16 -0
- package/src/ui/public/logo.svg +27 -0
- package/src/ui/public/vite.svg +1 -0
- package/src/ui/server/index.js +598 -0
- package/src/utils/index.js +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- 主分支 -->
|
|
3
|
+
<line x1="40" y1="40" x2="40" y2="160" stroke="#34495e" stroke-width="8" stroke-linecap="round" />
|
|
4
|
+
|
|
5
|
+
<!-- 特性分支 -->
|
|
6
|
+
<path d="M40,70 C60,70 80,90 80,110 L80,130" stroke="#3498db" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
7
|
+
|
|
8
|
+
<!-- 开发分支 -->
|
|
9
|
+
<path d="M40,100 C60,100 100,110 100,130 L100,160" stroke="#2ecc71" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
10
|
+
|
|
11
|
+
<!-- 修复分支 -->
|
|
12
|
+
<path d="M40,130 C60,130 120,140 120,160" stroke="#e74c3c" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
13
|
+
|
|
14
|
+
<!-- 合并点 -->
|
|
15
|
+
<path d="M80,130 C80,145 60,145 40,145" stroke="#3498db" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
16
|
+
|
|
17
|
+
<!-- 节点 -->
|
|
18
|
+
<circle cx="40" cy="40" r="10" fill="#34495e" />
|
|
19
|
+
<circle cx="40" cy="70" r="10" fill="#34495e" />
|
|
20
|
+
<circle cx="40" cy="100" r="10" fill="#34495e" />
|
|
21
|
+
<circle cx="40" cy="130" r="10" fill="#34495e" />
|
|
22
|
+
<circle cx="40" cy="145" r="10" fill="#34495e" />
|
|
23
|
+
<circle cx="40" cy="160" r="10" fill="#34495e" />
|
|
24
|
+
<circle cx="80" cy="130" r="10" fill="#3498db" />
|
|
25
|
+
<circle cx="100" cy="160" r="10" fill="#2ecc71" />
|
|
26
|
+
<circle cx="120" cy="160" r="10" fill="#e74c3c" />
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Zen-GitSync - Git同步工具</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-kfMX1bxz.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-DxvF30ca.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/vendor-Dp0FkvMe.css">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BHmYZROy.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="app"></div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- 主分支 -->
|
|
3
|
+
<line x1="40" y1="40" x2="40" y2="160" stroke="#34495e" stroke-width="8" stroke-linecap="round" />
|
|
4
|
+
|
|
5
|
+
<!-- 特性分支 -->
|
|
6
|
+
<path d="M40,70 C60,70 80,90 80,110 L80,130" stroke="#3498db" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
7
|
+
|
|
8
|
+
<!-- 开发分支 -->
|
|
9
|
+
<path d="M40,100 C60,100 100,110 100,130 L100,160" stroke="#2ecc71" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
10
|
+
|
|
11
|
+
<!-- 修复分支 -->
|
|
12
|
+
<path d="M40,130 C60,130 120,140 120,160" stroke="#e74c3c" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
13
|
+
|
|
14
|
+
<!-- 合并点 -->
|
|
15
|
+
<path d="M80,130 C80,145 60,145 40,145" stroke="#3498db" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
16
|
+
|
|
17
|
+
<!-- 节点 -->
|
|
18
|
+
<circle cx="40" cy="40" r="10" fill="#34495e" />
|
|
19
|
+
<circle cx="40" cy="70" r="10" fill="#34495e" />
|
|
20
|
+
<circle cx="40" cy="100" r="10" fill="#34495e" />
|
|
21
|
+
<circle cx="40" cy="130" r="10" fill="#34495e" />
|
|
22
|
+
<circle cx="40" cy="145" r="10" fill="#34495e" />
|
|
23
|
+
<circle cx="40" cy="160" r="10" fill="#34495e" />
|
|
24
|
+
<circle cx="80" cy="130" r="10" fill="#3498db" />
|
|
25
|
+
<circle cx="100" cy="160" r="10" fill="#2ecc71" />
|
|
26
|
+
<circle cx="120" cy="160" r="10" fill="#e74c3c" />
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { execGitCommand } from '../../utils/index.js';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import config from '../../config.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
// import { Server } from 'socket.io';
|
|
11
|
+
// import { exec } from 'child_process';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const configManager = config; // 确保 configManager 可用
|
|
16
|
+
|
|
17
|
+
async function startUIServer() {
|
|
18
|
+
const app = express();
|
|
19
|
+
const httpServer = createServer(app);
|
|
20
|
+
// const io = new Server(httpServer);
|
|
21
|
+
|
|
22
|
+
// 添加全局中间件来解析JSON请求体
|
|
23
|
+
app.use(express.json());
|
|
24
|
+
|
|
25
|
+
// // 启动前端Vue应用
|
|
26
|
+
// const clientPath = path.join(__dirname, '../client');
|
|
27
|
+
// console.log(`正在启动前端应用,路径: ${clientPath}`);
|
|
28
|
+
|
|
29
|
+
// const vueProcess = exec('npm run dev', { cwd: clientPath }, (error) => {
|
|
30
|
+
// if (error) {
|
|
31
|
+
// console.error('启动前端应用失败:', error);
|
|
32
|
+
// }
|
|
33
|
+
// });
|
|
34
|
+
|
|
35
|
+
// vueProcess.stdout.on('data', (data) => {
|
|
36
|
+
// console.log(`前端输出: ${data}`);
|
|
37
|
+
// });
|
|
38
|
+
|
|
39
|
+
// vueProcess.stderr.on('data', (data) => {
|
|
40
|
+
// console.error(`前端错误: ${data}`);
|
|
41
|
+
// });
|
|
42
|
+
|
|
43
|
+
// 静态文件服务
|
|
44
|
+
app.use(express.static(path.join(__dirname, '../public')));
|
|
45
|
+
|
|
46
|
+
// API路由
|
|
47
|
+
app.get('/api/status', async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const { stdout } = await execGitCommand('git status');
|
|
50
|
+
res.json({ status: stdout });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
res.status(500).json({ error: error.message });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
app.get('/api/status_porcelain', async (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const { stdout } = await execGitCommand('git status --porcelain');
|
|
58
|
+
res.json({ status: stdout });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
res.status(500).json({ error: error.message });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 获取当前分支
|
|
65
|
+
app.get('/api/branch', async (req, res) => {
|
|
66
|
+
try {
|
|
67
|
+
const { stdout } = await execGitCommand('git rev-parse --abbrev-ref HEAD');
|
|
68
|
+
res.json({ branch: stdout.trim() });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
res.status(500).json({ error: error.message });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 获取所有分支
|
|
75
|
+
app.get('/api/branches', async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
// 获取本地分支
|
|
78
|
+
const { stdout: localBranches } = await execGitCommand('git branch --format="%(refname:short)"');
|
|
79
|
+
// 获取远程分支(过滤掉 origin/HEAD 和 origin)
|
|
80
|
+
const { stdout: remoteBranches } = await execGitCommand('git branch -r --format="%(refname:short)"');
|
|
81
|
+
|
|
82
|
+
// 合并并去重
|
|
83
|
+
const allBranches = [...new Set([
|
|
84
|
+
...localBranches.split('\n')
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.filter(b => !b.startsWith('* ')), // 过滤掉HEAD指针
|
|
87
|
+
...remoteBranches.split('\n')
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.filter(b => b.includes('/')) // 过滤掉单纯的 origin
|
|
90
|
+
.map(b => b.split('/')[1]) // 提取真正的分支名称
|
|
91
|
+
])];
|
|
92
|
+
|
|
93
|
+
res.json({ branches: allBranches });
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('获取分支列表失败:', error);
|
|
96
|
+
res.status(500).json({ error: error.message });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 创建新分支
|
|
101
|
+
app.post('/api/create-branch', express.json(), async (req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const { newBranchName, baseBranch } = req.body;
|
|
104
|
+
|
|
105
|
+
if (!newBranchName) {
|
|
106
|
+
return res.status(400).json({ success: false, error: '分支名称不能为空' });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 构建创建分支的命令
|
|
110
|
+
let command = `git branch ${newBranchName}`;
|
|
111
|
+
|
|
112
|
+
// 如果指定了基础分支,则基于该分支创建
|
|
113
|
+
if (baseBranch) {
|
|
114
|
+
command = `git branch ${newBranchName} ${baseBranch}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 执行创建分支命令
|
|
118
|
+
await execGitCommand(command);
|
|
119
|
+
|
|
120
|
+
// 切换到新创建的分支
|
|
121
|
+
await execGitCommand(`git checkout ${newBranchName}`);
|
|
122
|
+
|
|
123
|
+
res.json({ success: true, branch: newBranchName });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('创建分支失败:', error);
|
|
126
|
+
res.status(500).json({ success: false, error: error.message });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 切换分支
|
|
131
|
+
app.post('/api/checkout', async (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
const { branch } = req.body;
|
|
134
|
+
if (!branch) {
|
|
135
|
+
return res.status(400).json({ success: false, error: '分支名称不能为空' });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 执行分支切换
|
|
139
|
+
await execGitCommand(`git checkout ${branch}`);
|
|
140
|
+
|
|
141
|
+
res.json({ success: true });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('切换分支失败:', error);
|
|
144
|
+
res.status(500).json({ success: false, error: error.message });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// 获取Git用户配置信息
|
|
149
|
+
app.get('/api/user-info', async (req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
// 获取用户名
|
|
152
|
+
const { stdout: userName } = await execGitCommand('git config user.name');
|
|
153
|
+
// 获取用户邮箱
|
|
154
|
+
const { stdout: userEmail } = await execGitCommand('git config user.email');
|
|
155
|
+
|
|
156
|
+
res.json({
|
|
157
|
+
name: userName.trim(),
|
|
158
|
+
email: userEmail.trim()
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
res.status(500).json({ error: error.message });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// 新增获取当前工作目录接口
|
|
166
|
+
app.get('/api/current_directory', async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const directory = process.cwd();
|
|
169
|
+
|
|
170
|
+
// 检查当前目录是否是Git仓库
|
|
171
|
+
try {
|
|
172
|
+
await execGitCommand('git rev-parse --is-inside-work-tree');
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return res.status(400).json({
|
|
175
|
+
error: '当前目录不是一个Git仓库',
|
|
176
|
+
directory,
|
|
177
|
+
isGitRepo: false
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
res.json({
|
|
182
|
+
directory,
|
|
183
|
+
isGitRepo: true
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
res.status(500).json({ error: error.message });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 新增切换工作目录接口
|
|
191
|
+
app.post('/api/change_directory', async (req, res) => {
|
|
192
|
+
try {
|
|
193
|
+
|
|
194
|
+
const { path } = req.body;
|
|
195
|
+
|
|
196
|
+
if (!path) {
|
|
197
|
+
return res.status(400).json({ success: false, error: '目录路径不能为空' });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
process.chdir(path);
|
|
202
|
+
const newDirectory = process.cwd();
|
|
203
|
+
|
|
204
|
+
// 检查新目录是否是Git仓库
|
|
205
|
+
try {
|
|
206
|
+
await execGitCommand('git rev-parse --is-inside-work-tree');
|
|
207
|
+
res.json({
|
|
208
|
+
success: true,
|
|
209
|
+
directory: newDirectory,
|
|
210
|
+
isGitRepo: true
|
|
211
|
+
});
|
|
212
|
+
} catch (error) {
|
|
213
|
+
res.json({
|
|
214
|
+
success: true,
|
|
215
|
+
directory: newDirectory,
|
|
216
|
+
isGitRepo: false,
|
|
217
|
+
warning: '新目录不是一个Git仓库'
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
res.status(400).json({
|
|
222
|
+
success: false,
|
|
223
|
+
error: `切换到目录 "${path}" 失败: ${error.message}`
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
res.status(500).json({ error: error.message });
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// 获取目录内容(用于浏览目录)
|
|
232
|
+
app.get('/api/browse_directory', async (req, res) => {
|
|
233
|
+
try {
|
|
234
|
+
|
|
235
|
+
// 获取要浏览的目录路径,如果没有提供,则使用当前目录
|
|
236
|
+
const directoryPath = req.query.path || process.cwd();
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
// 读取目录内容
|
|
240
|
+
const items = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
241
|
+
|
|
242
|
+
// 分离文件夹和文件
|
|
243
|
+
const directories = [];
|
|
244
|
+
const files = [];
|
|
245
|
+
|
|
246
|
+
for (const item of items) {
|
|
247
|
+
const fullPath = path.join(directoryPath, item.name);
|
|
248
|
+
if (item.isDirectory()) {
|
|
249
|
+
directories.push({
|
|
250
|
+
name: item.name,
|
|
251
|
+
path: fullPath,
|
|
252
|
+
type: 'directory'
|
|
253
|
+
});
|
|
254
|
+
} else if (item.isFile()) {
|
|
255
|
+
files.push({
|
|
256
|
+
name: item.name,
|
|
257
|
+
path: fullPath,
|
|
258
|
+
type: 'file'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 优先显示目录,然后是文件,都按字母排序
|
|
264
|
+
directories.sort((a, b) => a.name.localeCompare(b.name));
|
|
265
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
266
|
+
|
|
267
|
+
// 获取父目录路径
|
|
268
|
+
const parentPath = path.dirname(directoryPath);
|
|
269
|
+
|
|
270
|
+
res.json({
|
|
271
|
+
success: true,
|
|
272
|
+
currentPath: directoryPath,
|
|
273
|
+
parentPath: parentPath !== directoryPath ? parentPath : null,
|
|
274
|
+
items: [...directories, ...files]
|
|
275
|
+
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
res.status(400).json({
|
|
278
|
+
success: false,
|
|
279
|
+
error: `无法读取目录 "${directoryPath}": ${error.message}`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
res.status(500).json({ error: error.message });
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// 获取配置
|
|
288
|
+
app.get('/api/config/getConfig', async (req, res) => {
|
|
289
|
+
try {
|
|
290
|
+
const config = await configManager.loadConfig()
|
|
291
|
+
res.json(config)
|
|
292
|
+
} catch (error) {
|
|
293
|
+
res.status(500).json({ error: error.message })
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// 保存描述模板
|
|
298
|
+
app.post('/api/config/save-template', express.json(), async (req, res) => {
|
|
299
|
+
try {
|
|
300
|
+
const { template, type } = req.body
|
|
301
|
+
|
|
302
|
+
if (!template || !type) {
|
|
303
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const config = await configManager.loadConfig()
|
|
307
|
+
|
|
308
|
+
if (type === 'description') {
|
|
309
|
+
// 确保描述模板数组存在
|
|
310
|
+
if (!config.descriptionTemplates) {
|
|
311
|
+
config.descriptionTemplates = []
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 检查是否已存在相同模板
|
|
315
|
+
if (!config.descriptionTemplates.includes(template)) {
|
|
316
|
+
config.descriptionTemplates.push(template)
|
|
317
|
+
await configManager.saveConfig(config)
|
|
318
|
+
}
|
|
319
|
+
} else if (type === 'scope') {
|
|
320
|
+
// 确保作用域模板数组存在
|
|
321
|
+
if (!config.scopeTemplates) {
|
|
322
|
+
config.scopeTemplates = []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 检查是否已存在相同模板
|
|
326
|
+
if (!config.scopeTemplates.includes(template)) {
|
|
327
|
+
config.scopeTemplates.push(template)
|
|
328
|
+
await configManager.saveConfig(config)
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
res.json({ success: true })
|
|
335
|
+
} catch (error) {
|
|
336
|
+
res.status(500).json({ success: false, error: error.message })
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// 删除描述模板
|
|
341
|
+
app.post('/api/config/delete-template', express.json(), async (req, res) => {
|
|
342
|
+
try {
|
|
343
|
+
const { template, type } = req.body
|
|
344
|
+
|
|
345
|
+
if (!template || !type) {
|
|
346
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const config = await configManager.loadConfig()
|
|
350
|
+
|
|
351
|
+
if (type === 'description') {
|
|
352
|
+
// 确保描述模板数组存在
|
|
353
|
+
if (config.descriptionTemplates) {
|
|
354
|
+
const index = config.descriptionTemplates.indexOf(template)
|
|
355
|
+
if (index !== -1) {
|
|
356
|
+
config.descriptionTemplates.splice(index, 1)
|
|
357
|
+
await configManager.saveConfig(config)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} else if (type === 'scope') {
|
|
361
|
+
// 确保作用域模板数组存在
|
|
362
|
+
if (config.scopeTemplates) {
|
|
363
|
+
const index = config.scopeTemplates.indexOf(template)
|
|
364
|
+
if (index !== -1) {
|
|
365
|
+
config.scopeTemplates.splice(index, 1)
|
|
366
|
+
await configManager.saveConfig(config)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
res.json({ success: true })
|
|
374
|
+
} catch (error) {
|
|
375
|
+
res.status(500).json({ success: false, error: error.message })
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// 提交更改
|
|
380
|
+
app.post('/api/commit', express.json(), async (req, res) => {
|
|
381
|
+
try {
|
|
382
|
+
const { message, hasNewlines, noVerify } = req.body;
|
|
383
|
+
|
|
384
|
+
// 构建 git commit 命令
|
|
385
|
+
let commitCommand = 'git commit';
|
|
386
|
+
|
|
387
|
+
// 如果消息包含换行符,使用文件方式提交
|
|
388
|
+
if (hasNewlines) {
|
|
389
|
+
// 创建临时文件存储提交信息
|
|
390
|
+
const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
|
|
391
|
+
await fs.writeFile(tempFile, message);
|
|
392
|
+
commitCommand += ` -F "${tempFile}"`;
|
|
393
|
+
} else {
|
|
394
|
+
// 否则直接在命令行中提供消息
|
|
395
|
+
commitCommand += ` -m "${message}"`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 添加 --no-verify 参数
|
|
399
|
+
if (noVerify) {
|
|
400
|
+
commitCommand += ' --no-verify';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
console.log(`commitCommand ==>`, commitCommand);
|
|
404
|
+
// 执行提交命令
|
|
405
|
+
await execGitCommand(commitCommand);
|
|
406
|
+
|
|
407
|
+
// 如果使用了临时文件,删除它
|
|
408
|
+
if (hasNewlines) {
|
|
409
|
+
const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
|
|
410
|
+
await fs.unlink(tempFile).catch(() => {});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
res.json({ success: true });
|
|
414
|
+
} catch (error) {
|
|
415
|
+
res.status(500).json({ success: false, error: error.message });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// 添加 add 接口
|
|
420
|
+
app.post('/api/add', async (req, res) => {
|
|
421
|
+
try {
|
|
422
|
+
// 执行 git add . 命令添加所有更改
|
|
423
|
+
await execGitCommand('git add .');
|
|
424
|
+
res.json({ success: true });
|
|
425
|
+
} catch (error) {
|
|
426
|
+
res.status(500).json({ success: false, error: error.message });
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// 推送更改
|
|
431
|
+
app.post('/api/push', async (req, res) => {
|
|
432
|
+
try {
|
|
433
|
+
await execGitCommand('git push');
|
|
434
|
+
res.json({ success: true });
|
|
435
|
+
} catch (error) {
|
|
436
|
+
res.status(500).json({ error: error.message });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// 获取日志
|
|
441
|
+
app.get('/api/log', async (req, res) => {
|
|
442
|
+
try {
|
|
443
|
+
// 获取请求参数中的数量限制,默认为100
|
|
444
|
+
const limit = req.query.all === 'true' ? '' : '-n 100';
|
|
445
|
+
|
|
446
|
+
// 修改 git log 命令,添加 %ae 参数来获取作者邮箱
|
|
447
|
+
const { stdout } = await execGitCommand(`git log --all --pretty=format:"%h|%an|%ae|%ad|%s|%D" --date=short ${limit}`);
|
|
448
|
+
const logs = stdout.split('\n').map(line => {
|
|
449
|
+
const [hash, author, email, date, message, refs] = line.split('|');
|
|
450
|
+
|
|
451
|
+
// 从引用信息中提取分支名称
|
|
452
|
+
let branch = null;
|
|
453
|
+
if (refs) {
|
|
454
|
+
// 提取所有引用信息,而不仅仅是第一个匹配
|
|
455
|
+
branch = refs.trim();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return { hash, author, email, date, message, branch };
|
|
459
|
+
});
|
|
460
|
+
res.json(logs);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
res.status(500).json({ error: error.message });
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 获取文件差异
|
|
467
|
+
app.get('/api/diff', async (req, res) => {
|
|
468
|
+
try {
|
|
469
|
+
const filePath = req.query.file;
|
|
470
|
+
|
|
471
|
+
if (!filePath) {
|
|
472
|
+
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 执行git diff命令获取文件差异
|
|
476
|
+
const { stdout } = await execGitCommand(`git diff -- "${filePath}"`);
|
|
477
|
+
|
|
478
|
+
res.json({ diff: stdout });
|
|
479
|
+
} catch (error) {
|
|
480
|
+
res.status(500).json({ error: error.message });
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// 撤回文件修改
|
|
485
|
+
app.post('/api/revert_file', async (req, res) => {
|
|
486
|
+
try {
|
|
487
|
+
const { filePath } = req.body;
|
|
488
|
+
|
|
489
|
+
if (!filePath) {
|
|
490
|
+
return res.status(400).json({
|
|
491
|
+
success: false,
|
|
492
|
+
error: '缺少文件路径参数'
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 检查文件状态:未跟踪文件需要删除,修改文件需要恢复
|
|
497
|
+
const { stdout: statusOutput } = await execGitCommand(`git status --porcelain -- "${filePath}"`);
|
|
498
|
+
|
|
499
|
+
// 未跟踪的文件 (??), 需要删除它
|
|
500
|
+
if (statusOutput.startsWith('??')) {
|
|
501
|
+
try {
|
|
502
|
+
await fs.unlink(filePath);
|
|
503
|
+
return res.json({ success: true, message: '未跟踪的文件已删除' });
|
|
504
|
+
} catch (error) {
|
|
505
|
+
return res.status(500).json({
|
|
506
|
+
success: false,
|
|
507
|
+
error: `删除文件失败: ${error.message}`
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// 已暂存的文件,先取消暂存
|
|
512
|
+
else if (statusOutput.startsWith('A ') || statusOutput.startsWith('M ') || statusOutput.startsWith('D ')) {
|
|
513
|
+
// 先取消暂存
|
|
514
|
+
await execGitCommand(`git reset HEAD -- "${filePath}"`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 已修改文件,取消所有本地修改
|
|
518
|
+
if (statusOutput) {
|
|
519
|
+
await execGitCommand(`git checkout -- "${filePath}"`);
|
|
520
|
+
return res.json({ success: true, message: '文件修改已撤回' });
|
|
521
|
+
} else {
|
|
522
|
+
return res.status(400).json({
|
|
523
|
+
success: false,
|
|
524
|
+
error: '文件没有修改或不存在'
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.error('撤回文件修改失败:', error);
|
|
529
|
+
res.status(500).json({
|
|
530
|
+
success: false,
|
|
531
|
+
error: `撤回文件修改失败: ${error.message}`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// Socket.io 实时更新
|
|
537
|
+
// io.on('connection', (socket) => {
|
|
538
|
+
// console.log('客户端已连接');
|
|
539
|
+
|
|
540
|
+
// // 定期发送状态更新
|
|
541
|
+
// const statusInterval = setInterval(async () => {
|
|
542
|
+
// try {
|
|
543
|
+
// const { stdout } = await execGitCommand('git status');
|
|
544
|
+
// socket.emit('status_update', { status: stdout });
|
|
545
|
+
// } catch (error) {
|
|
546
|
+
// console.error('状态更新错误:', error);
|
|
547
|
+
// }
|
|
548
|
+
// }, 5000);
|
|
549
|
+
|
|
550
|
+
// socket.on('disconnect', () => {
|
|
551
|
+
// clearInterval(statusInterval);
|
|
552
|
+
// console.log('客户端已断开连接');
|
|
553
|
+
// });
|
|
554
|
+
// });
|
|
555
|
+
|
|
556
|
+
// 启动服务器
|
|
557
|
+
const PORT = 3000;
|
|
558
|
+
httpServer.listen(PORT, () => {
|
|
559
|
+
console.log(`后端API服务器已启动: http://localhost:${PORT}`);
|
|
560
|
+
open(`http://localhost:${PORT}`);
|
|
561
|
+
}).on('error', async (err) => {
|
|
562
|
+
if (err.code === 'EADDRINUSE') {
|
|
563
|
+
console.log(`端口 ${PORT} 被占用,尝试其他端口...`);
|
|
564
|
+
let newPort = PORT + 1;
|
|
565
|
+
while (newPort < PORT + 100) {
|
|
566
|
+
try {
|
|
567
|
+
await new Promise((resolve, reject) => {
|
|
568
|
+
httpServer.listen(newPort, () => {
|
|
569
|
+
console.log(`后端API服务器已启动: http://localhost:${newPort}`);
|
|
570
|
+
open(`http://localhost:${newPort}`);
|
|
571
|
+
resolve();
|
|
572
|
+
}).on('error', (e) => {
|
|
573
|
+
if (e.code === 'EADDRINUSE') {
|
|
574
|
+
console.log(`端口 ${newPort} 也被占用,继续尝试...`);
|
|
575
|
+
newPort++;
|
|
576
|
+
reject(e);
|
|
577
|
+
} else {
|
|
578
|
+
reject(e);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
} catch (e) {
|
|
584
|
+
if (newPort >= PORT + 100) {
|
|
585
|
+
console.error('无法找到可用端口,请手动指定端口');
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
console.error('服务器启动失败:', err);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export default startUIServer;
|
|
598
|
+
|
package/src/utils/index.js
CHANGED
|
@@ -301,6 +301,7 @@ Options:
|
|
|
301
301
|
--no-diff Skip displaying git diff
|
|
302
302
|
addScript Add "g:y": "g -y" to package.json scripts
|
|
303
303
|
addResetScript Add "g:reset": "git reset --hard origin/<current-branch>" to package.json scripts
|
|
304
|
+
ui Launch graphical user interface (v2.0.0)
|
|
304
305
|
|
|
305
306
|
Example:
|
|
306
307
|
g -m "Initial commit" Commit with a custom message
|
|
@@ -326,6 +327,9 @@ Run in the background across platforms:
|
|
|
326
327
|
Linux/macOS:
|
|
327
328
|
nohup g -y --path=your-folder --interval=600 > git-autocommit.log 2>&1 &
|
|
328
329
|
|
|
330
|
+
Start GUI interface:
|
|
331
|
+
g ui
|
|
332
|
+
|
|
329
333
|
Stop all monitoring processes:
|
|
330
334
|
Windows: Terminate the Node.js process in the Task Manager.
|
|
331
335
|
Linux/macOS:
|