@wwkit/opm 1.0.1 → 1.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.
@@ -0,0 +1,378 @@
1
+ /**
2
+ * opm git 命令组 — Git 配置管理
3
+ *
4
+ * 命令:init / config / user / log / export / import / help
5
+ * 聚焦冷门或多步骤操作,log 支持指定 workdir 免切换目录查看提交历史。
6
+ * export 保存两个 commit 之间的差异到文件,默认从 mark 到 HEAD。
7
+ * mark 用 lightweight git tag 标记基准 commit,对用户透明。
8
+ */
9
+
10
+ import path from 'node:path'
11
+ import fs from 'node:fs'
12
+ import { execSync } from 'node:child_process'
13
+ import { parseFlags } from '../../cli/helpers/args.js'
14
+ import { output } from '../../formatter.js'
15
+ import {
16
+ listConfig, getConfigValue, setConfigValue, unsetConfigValue,
17
+ getUser, setUser,
18
+ initGitConfig, saveDiff, importDiff,
19
+ getMark, setMark, unsetMark,
20
+ getGitVersion, installGit, uninstallGit, upgradeGit,
21
+ } from './config.js'
22
+
23
+ const ACTIONS = {
24
+ version: { desc: 'Show installed git version' },
25
+ install: { desc: 'Install git via system package manager (dnf/yum/apt/apk)' },
26
+ uninstall: { desc: 'Uninstall git via system package manager' },
27
+ upgrade: { desc: 'Upgrade git via system package manager' },
28
+ init: { desc: 'Initialize git global config from opm config (user/ssl/pull.rebase)' },
29
+ config: { desc: 'View/set/unset git config (with --scope global|local|system)' },
30
+ user: { desc: 'View/set user.name and user.email' },
31
+ log: { desc: 'Show commit history (with -d/--dir for workdir, -n for count)' },
32
+ export: { desc: 'Export diff to file (default: mark -> HEAD, or -f <n> override)' },
33
+ import: { desc: 'Import diff file into working tree (git apply --reject)' },
34
+ help: { desc: 'Show this help' },
35
+ }
36
+
37
+ export class GitGroup {
38
+ constructor() {
39
+ this.name = 'git'
40
+ this.desc = 'Git config management (init/config/user/log/export)'
41
+ }
42
+
43
+ async run(argv) {
44
+ const [action, ...rest] = argv
45
+
46
+ if (!action || action === '-h' || action === '--help' || action === 'help') {
47
+ this.printHelp()
48
+ return
49
+ }
50
+
51
+ const parsed = parseFlags(rest)
52
+
53
+ switch (action) {
54
+ case 'version':
55
+ return this._version()
56
+ case 'install':
57
+ return this._install(parsed)
58
+ case 'uninstall':
59
+ return this._uninstall()
60
+ case 'upgrade':
61
+ return this._upgrade(parsed)
62
+ case 'init':
63
+ return this._init(parsed)
64
+ case 'config':
65
+ return this._config(parsed)
66
+ case 'user':
67
+ return this._user(parsed)
68
+ case 'log':
69
+ return this._log(parsed)
70
+ case 'export':
71
+ return this._export(parsed)
72
+ case 'import':
73
+ return this._import(parsed)
74
+ default:
75
+ console.error(`Unknown action: ${action}`)
76
+ this.printHelp()
77
+ process.exit(1)
78
+ }
79
+ }
80
+
81
+ _resolveProxy(parsed) {
82
+ let proxy = parsed.flags.p || parsed.flags.proxy || ''
83
+ if (proxy === 'true') proxy = ''
84
+ return proxy
85
+ }
86
+
87
+ _version() {
88
+ const result = getGitVersion()
89
+ output(result)
90
+ }
91
+
92
+ _install(parsed) {
93
+ const proxy = this._resolveProxy(parsed)
94
+ const result = installGit(proxy)
95
+ output(result)
96
+ }
97
+
98
+ _uninstall() {
99
+ const result = uninstallGit()
100
+ output(result)
101
+ }
102
+
103
+ _upgrade(parsed) {
104
+ const proxy = this._resolveProxy(parsed)
105
+ const result = upgradeGit(proxy)
106
+ output(result)
107
+ }
108
+
109
+ _getScope(parsed) {
110
+ return parsed.flags.scope || 'global'
111
+ }
112
+
113
+ async _init(parsed) {
114
+ const force = parsed.flags.f === 'true' || parsed.flags.force === 'true'
115
+ const result = await initGitConfig(force)
116
+ output(result)
117
+ }
118
+
119
+ async _config(parsed) {
120
+ const scope = this._getScope(parsed)
121
+ const unsetKey = parsed.flags.unset || ''
122
+
123
+ if (unsetKey) {
124
+ const ok = unsetConfigValue(unsetKey, scope)
125
+ output({ scope, key: unsetKey, unset: ok })
126
+ return
127
+ }
128
+
129
+ const [key, value] = parsed.positional
130
+
131
+ if (key && value) {
132
+ const ok = setConfigValue(key, value, scope)
133
+ output({ scope, key, value, set: ok })
134
+ return
135
+ }
136
+
137
+ if (key) {
138
+ const val = getConfigValue(key, scope)
139
+ output({ scope, key, value: val })
140
+ return
141
+ }
142
+
143
+ const result = listConfig(scope)
144
+ output({ scope, ...result })
145
+ }
146
+
147
+ async _user(parsed) {
148
+ const scope = this._getScope(parsed)
149
+ const name = parsed.flags.n || parsed.flags.name || ''
150
+ const email = parsed.flags.e || parsed.flags.email || ''
151
+
152
+ if (name || email) {
153
+ const result = setUser(name, email, scope)
154
+ output({ scope, ...result })
155
+ return
156
+ }
157
+
158
+ const result = getUser(scope)
159
+ output({ scope, ...result })
160
+ }
161
+
162
+ _log(parsed) {
163
+ const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
164
+ const resolvedDir = path.resolve(workDir)
165
+ if (!this._getGitDir(resolvedDir)) {
166
+ console.error(`Not a git repository: ${resolvedDir}`)
167
+ process.exit(1)
168
+ }
169
+ const n = parseInt(parsed.flags.n || parsed.flags.count || '10', 10) || 10
170
+ const opts = { encoding: 'utf8', timeout: 5000, cwd: resolvedDir, stdio: ['pipe', 'pipe', 'pipe'] }
171
+ const raw = execSync(`git log --oneline -${n}`, opts).trim()
172
+ if (!raw) {
173
+ output({ commits: [], message: 'No commits yet' })
174
+ return
175
+ }
176
+ const commits = raw.split('\n').map((line) => {
177
+ const idx = line.indexOf(' ')
178
+ return { hash: line.slice(0, idx), subject: line.slice(idx + 1) }
179
+ })
180
+ output({ dir: resolvedDir, count: commits.length, commits })
181
+ }
182
+
183
+ _printRecentLog(workDir, count = 5) {
184
+ const resolvedDir = path.resolve(workDir)
185
+ if (!this._getGitDir(resolvedDir)) return
186
+ const opts = { encoding: 'utf8', timeout: 5000, cwd: resolvedDir, stdio: ['pipe', 'pipe', 'pipe'] }
187
+ try {
188
+ const raw = execSync(`git log --oneline -${count}`, opts).trim()
189
+ if (raw) {
190
+ console.error(`\nRecent ${count} commits:`)
191
+ for (const line of raw.split('\n')) {
192
+ console.error(` ${line}`)
193
+ }
194
+ }
195
+ } catch {}
196
+ }
197
+
198
+ async _export(parsed) {
199
+ const [sub] = parsed.positional
200
+
201
+ if (sub === 'mark') {
202
+ return this._exportMark(parsed)
203
+ }
204
+
205
+ return this._exportDiff(parsed)
206
+ }
207
+
208
+ _exportMark(parsed) {
209
+ const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
210
+ const ref = parsed.positional[1]
211
+
212
+ if (parsed.flags.unset === 'true') {
213
+ const ok = unsetMark(workDir)
214
+ output({ mark: 'removed', ok })
215
+ return
216
+ }
217
+
218
+ if (!ref) {
219
+ const mark = getMark(workDir)
220
+ if (!mark) {
221
+ output({ mark: 'not-set' })
222
+ console.error('\nTips: mark not set, set a diff base first:')
223
+ console.error(' opm git export mark HEAD # mark current HEAD as diff base')
224
+ console.error(' opm git export mark <commit> # mark a commit as diff base')
225
+ console.error(' opm git log -d <dir> -n 10 # view commit history without cd')
226
+ this._printRecentLog(workDir)
227
+ return
228
+ }
229
+ output({ mark: 'set', ...mark })
230
+ console.error('\nTips:')
231
+ console.error(' opm git log -d <dir> -n 10 # view commit history without cd')
232
+ console.error(' opm git export mark <commit> # change diff base to another commit')
233
+ console.error(' opm git export mark --unset # remove current mark')
234
+ console.error(' opm git export # export diff (mark -> HEAD)')
235
+ this._printRecentLog(workDir)
236
+ return
237
+ }
238
+
239
+ const resolvedRef = ref === '0' || ref.toLowerCase() === 'head' ? 'HEAD' : ref
240
+ const result = setMark(workDir, resolvedRef)
241
+ output({ mark: 'set', ...result })
242
+ }
243
+
244
+ _exportDiff(parsed) {
245
+ const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
246
+ const resolvedDir = path.resolve(workDir)
247
+ const gitDir = this._getGitDir(resolvedDir)
248
+ if (!gitDir) {
249
+ console.error(`Not a git repository: ${resolvedDir}`)
250
+ process.exit(1)
251
+ }
252
+
253
+ const fromRaw = parsed.flags.f || parsed.flags.from || ''
254
+ let fromRef
255
+ if (fromRaw) {
256
+ const n = parseInt(fromRaw, 10)
257
+ fromRef = isNaN(n) ? fromRaw : `HEAD~${n}`
258
+ } else {
259
+ const mark = getMark(resolvedDir)
260
+ if (!mark) {
261
+ console.error('No mark found. Set a mark first:')
262
+ console.error(' opm git export mark HEAD # mark current HEAD as diff base')
263
+ console.error(' opm git export mark <commit> # mark a commit as diff base')
264
+ console.error(' opm git log -d <dir> -n 10 # view commit history without cd')
265
+ process.exit(1)
266
+ }
267
+ fromRef = mark.commit
268
+ }
269
+
270
+ let outFile = parsed.flags.o || parsed.flags.output || ''
271
+ if (!outFile) {
272
+ const opts = { encoding: 'utf8', timeout: 5000, cwd: resolvedDir, stdio: ['pipe', 'pipe', 'pipe'] }
273
+ const gitRoot = execSync('git rev-parse --show-toplevel', opts).trim()
274
+ const name = parsed.flags.n || parsed.flags.name || ''
275
+ const fileName = name ? `${name}.diff` : (() => {
276
+ const fromShort = execSync(`git rev-parse --short ${fromRef}`, opts).trim()
277
+ const toShort = execSync('git rev-parse --short HEAD', opts).trim()
278
+ return `changes-${fromShort}-to-${toShort}.diff`
279
+ })()
280
+ outFile = path.join(gitRoot, '.webwork', 'git', fileName)
281
+ }
282
+
283
+ const filePath = path.resolve(outFile)
284
+ const dir = path.dirname(filePath)
285
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
286
+
287
+ const result = saveDiff(resolvedDir, filePath, fromRef)
288
+ output(result)
289
+ }
290
+
291
+ async _import(parsed) {
292
+ const diffFile = parsed.positional[0]
293
+ if (!diffFile) {
294
+ console.error('Usage: opm git import <difffile> [-d <workdir>]')
295
+ process.exit(1)
296
+ }
297
+
298
+ const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
299
+ const resolvedDir = path.resolve(workDir)
300
+ const gitDir = this._getGitDir(resolvedDir)
301
+ if (!gitDir) {
302
+ console.error(`Not a git repository: ${resolvedDir}`)
303
+ process.exit(1)
304
+ }
305
+
306
+ const result = importDiff(resolvedDir, diffFile)
307
+ output(result)
308
+ }
309
+
310
+ _getGitDir(dir) {
311
+ try {
312
+ execSync('git rev-parse --git-dir', { encoding: 'utf8', timeout: 5000, cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] })
313
+ return true
314
+ } catch {
315
+ return false
316
+ }
317
+ }
318
+
319
+ printHelp() {
320
+ const actionLines = Object.entries(ACTIONS)
321
+ .map(([sig, { desc }]) => ` ${sig.padEnd(12)} ${desc}`)
322
+ .join('\n')
323
+
324
+ console.log(`
325
+ Usage: opm git <action> [options]
326
+
327
+ Actions:
328
+ ${actionLines}
329
+
330
+ Export subcommands:
331
+ export mark [-d dir] Show current diff base mark
332
+ export mark 0|HEAD [-d dir] Set diff base mark to current HEAD
333
+ export mark <commit> [-d dir] Set diff base mark to a commit
334
+ export mark --unset [-d dir] Remove the diff base mark
335
+ export [-d dir] Export diff (mark -> HEAD)
336
+ export -f <n> [-d dir] Export diff (HEAD~n -> HEAD, override mark)
337
+ export -f <ref> [-d dir] Export diff (<ref> -> HEAD, override mark)
338
+
339
+ Options:
340
+ -d, --dir <dir> Git repository directory (default: cwd)
341
+ -p, --proxy <url> Proxy for install/upgrade
342
+ --scope <global|local|system> Config scope (default: global)
343
+ -n, --name <name> User name (user) / diff file name (export)
344
+ -n, --count <n> Number of commits to show (log, default: 10)
345
+ -e, --email <email> User email (user)
346
+ -o, --output <file> Output file path (export)
347
+ -f, --from <n>|<ref> Override from ref (export)
348
+ -f, --force Force overwrite existing config (init)
349
+ --unset <key> Unset a config key (config)
350
+ -h, --help Show this help
351
+
352
+ Examples:
353
+ opm git version Show installed git version
354
+ opm git install Install git via system package manager
355
+ opm git upgrade Upgrade git to latest
356
+ opm git uninstall Uninstall git
357
+ opm git init Initialize global config from opm config
358
+ opm git config List all global config
359
+ opm git config pull.rebase true Set pull to use rebase
360
+ opm git user Show current user.name and user.email
361
+ opm git log Show recent 10 commits (cwd)
362
+ opm git log -n 5 Show recent 5 commits (cwd)
363
+ opm git log -d /path/to/repo Show commits in specified repo
364
+ opm git export mark Show current diff base mark (cwd)
365
+ opm git export mark 0 Set diff base to current HEAD (cwd)
366
+ opm git export mark HEAD Set diff base to current HEAD (cwd)
367
+ opm git export mark 3af967b Set diff base at commit (cwd)
368
+ opm git export mark --unset Remove diff base mark (cwd)
369
+ opm git export mark abc -d /repo Set diff base at commit in /repo
370
+ opm git export Export diff in current repo (mark -> HEAD)
371
+ opm git export -f 2 Override: HEAD~2 -> HEAD (current repo)
372
+ opm git export -d /path/to/repo Export diff in specified repo
373
+ opm git export -n feature Save as feature.diff
374
+ opm git import file.diff Import diff file (current repo)
375
+ opm git import file.diff -d /repo Import diff file into specified repo
376
+ `)
377
+ }
378
+ }