-
-
Notifications
You must be signed in to change notification settings - Fork 34
/
git.ts
145 lines (134 loc) · 3.56 KB
/
git.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { exec, execSync, spawn, spawnSync } from 'child_process'
import { closeSync, openSync, writeSync } from 'fs'
import path from 'path'
import dedent from 'dedent'
import type { CallBackFn, CommitOptions } from '../types'
/**
* Use git diff command check the files are no staged files and no changes.
* @param {string} repoPath current repo path
* @param {CallBackFn} done callback function
* @param {boolean} stageAllFiles if true, will stage all files e.g `git commit -a`
* done callback function will return isClean
* @time cost 27 ms
*/
export const isGitClean = (repoPath: string, done: CallBackFn, stageAllFiles: boolean) => {
// if there are no staged files and no changes, but fails to throw an error with no staged files in dirty state.
exec(
`git diff --cached --no-ext-diff --name-only ${
stageAllFiles ? '&& git diff --no-ext-diff --name-only' : ''
}`,
{
maxBuffer: Infinity,
cwd: repoPath,
},
(error, stdout) => {
if (error)
return done(error)
const output = stdout || ''
done(null, output.trim().length === 0)
},
)
}
/**
* Get current repo path
* @time cost 12 ms
*/
export const getGitRootPath = () =>
spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim()
/**
* Get current repo .git folder path
* @time cost 12 ms
*/
export const getGitDirPath = (pwd: string) =>
execSync('git rev-parse --absolute-git-dir', { encoding: 'utf8', cwd: pwd }).trim()
/**
* Core
*
* Asynchronously git commit at a given path with a message
*/
export const gitCommit = (
repoPath: string,
message: string,
options: CommitOptions,
done: CallBackFn,
) => {
let called = false
/**
* nomorl mode. unuse git hook
* use `git cimmit -m "...message..."`
*/
if (!options.hookMode) {
const args = process.env.CzCommitSignGPG !== '1'
? ['commit', '-m', dedent(message), ...(options.args || [])]
: ['commit', '-S', '-m', dedent(message), ...(options.args || [])]
const child = spawn('git', args, {
cwd: repoPath,
stdio: options.quiet ? 'ignore' : 'inherit',
})
child.on('error', (e) => {
if (called)
return
called = true
done(e)
})
child.on('close', code => process.exit(code || 0))
child.on('exit', (code, signal) => {
if (called)
return
called = true
if (code) {
if (code === 128) {
console.warn(`
Git exited with code 128. Did you forget to run:
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
`)
}
done(Object.assign(new Error(`git exited with error code ${code}`), { code, signal }))
}
else {
// e.g: like control + c
done(null)
}
})
}
else {
/**
* use git hookMode.write the commit message into
* the .git/COMMIT_EDITMSG file
*/
const commitMsgFile = path.join(getGitDirPath(repoPath), 'COMMIT_EDITMSG')
try {
const fd = openSync(commitMsgFile, 'w')
try {
writeSync(fd, dedent(message))
done(null)
}
catch (e: any) {
done(e)
}
finally {
closeSync(fd)
}
}
catch (e) {
// for windows user
try {
const fd = openSync(commitMsgFile, 'w')
try {
writeSync(fd, dedent(message))
done(null)
}
catch (e: any) {
done(e)
}
finally {
closeSync(fd)
}
}
catch (e: any) {
done(e)
}
}
}
}