0
# Git Integration
1
2
Git utilities for user information and repository operations through simple-git integration with support for configuration access and GitHub username resolution.
3
4
## Capabilities
5
6
### Git User Information
7
8
Access git user configuration for author information and defaults.
9
10
```typescript { .api }
11
/**
12
* Git utilities interface
13
*/
14
interface GitUtil {
15
/**
16
* Retrieves user's name from Git in the global scope or project scope
17
* Takes what Git will use in the current context
18
* @returns Promise resolving to configured git name or undefined
19
*/
20
name(): Promise<string | undefined>;
21
22
/**
23
* Retrieves user's email from Git in the global scope or project scope
24
* Takes what Git will use in the current context
25
* @returns Promise resolving to configured git email or undefined
26
*/
27
email(): Promise<string | undefined>;
28
}
29
30
/**
31
* Git utilities instance
32
*/
33
readonly git: GitUtil;
34
```
35
36
**Usage Examples:**
37
38
```typescript
39
export default class MyGenerator extends Generator {
40
async prompting() {
41
// Get git user info for prompt defaults
42
const gitName = await this.git.name();
43
const gitEmail = await this.git.email();
44
45
this.answers = await this.prompt([
46
{
47
name: 'authorName',
48
message: 'Author name:',
49
default: gitName || 'Anonymous',
50
store: true
51
},
52
{
53
name: 'authorEmail',
54
message: 'Author email:',
55
default: gitEmail || '',
56
store: true
57
}
58
]);
59
}
60
61
async configuring() {
62
// Use git info in package.json
63
const author = {
64
name: this.answers.authorName || await this.git.name(),
65
email: this.answers.authorEmail || await this.git.email()
66
};
67
68
this.packageJson.merge({ author });
69
}
70
71
async initializing() {
72
// Validate git configuration
73
const gitName = await this.git.name();
74
const gitEmail = await this.git.email();
75
76
if (!gitName || !gitEmail) {
77
this.log('Warning: Git user.name and user.email should be configured');
78
this.log('Run: git config --global user.name "Your Name"');
79
this.log('Run: git config --global user.email "your.email@example.com"');
80
}
81
}
82
}
83
```
84
85
### Simple Git Integration
86
87
Direct access to simple-git instance for advanced git operations.
88
89
```typescript { .api }
90
/**
91
* Simple-git instance for advanced git operations
92
* Configured with current destination root and locale settings
93
*/
94
readonly simpleGit: SimpleGit;
95
```
96
97
**Usage Examples:**
98
99
```typescript
100
import { SimpleGit } from 'simple-git';
101
102
export default class MyGenerator extends Generator {
103
async writing() {
104
// Initialize git repository
105
if (this.answers.initGit && !await this.isGitRepo()) {
106
await this.simpleGit.init();
107
this.log('Initialized git repository');
108
}
109
110
// Set up git configuration for this project
111
if (this.answers.gitConfig) {
112
await this.simpleGit.addConfig('user.name', this.answers.authorName);
113
await this.simpleGit.addConfig('user.email', this.answers.authorEmail);
114
}
115
116
// Add gitignore
117
this.renderTemplate('gitignore.ejs', '.gitignore', {
118
nodeModules: true,
119
buildDirs: ['dist/', 'build/'],
120
logFiles: ['*.log', 'logs/']
121
});
122
}
123
124
async install() {
125
// Create initial commit after installation
126
if (this.answers.initialCommit) {
127
await this.simpleGit.add('.');
128
await this.simpleGit.commit('Initial commit\n\n🤖 Generated with Yeoman');
129
this.log('Created initial commit');
130
}
131
}
132
133
async end() {
134
// Set up remote repository
135
if (this.answers.remoteUrl) {
136
await this.simpleGit.addRemote('origin', this.answers.remoteUrl);
137
138
if (this.answers.pushToRemote) {
139
await this.simpleGit.push('origin', 'main');
140
this.log(`Pushed to remote: ${this.answers.remoteUrl}`);
141
}
142
}
143
}
144
145
// Helper method to check if directory is a git repo
146
async isGitRepo(): Promise<boolean> {
147
try {
148
await this.simpleGit.status();
149
return true;
150
} catch (error) {
151
return false;
152
}
153
}
154
}
155
```
156
157
### GitHub Integration (Deprecated)
158
159
GitHub utilities for username resolution (deprecated, will be removed in v8).
160
161
```typescript { .api }
162
/**
163
* @deprecated Will be removed in version 8
164
* GitHub utilities
165
*/
166
readonly github: {
167
/**
168
* @deprecated Will be removed in version 8. Use 'github-username' package with await this.git.email() result instead
169
* Retrieves GitHub's username from the GitHub API using email
170
* @returns Promise resolving to GitHub username or undefined
171
*/
172
username(): Promise<string | undefined>;
173
};
174
```
175
176
**Usage Example:**
177
178
```typescript
179
export default class MyGenerator extends Generator {
180
async prompting() {
181
// Get GitHub username (deprecated - use alternative approach)
182
let githubUsername;
183
184
try {
185
// Deprecated approach
186
githubUsername = await this.github.username();
187
} catch (error) {
188
// Alternative approach using email
189
const email = await this.git.email();
190
if (email) {
191
// Manual username derivation or use github-username package
192
githubUsername = email.split('@')[0];
193
}
194
}
195
196
this.answers = await this.prompt([
197
{
198
name: 'githubUsername',
199
message: 'GitHub username:',
200
default: githubUsername,
201
when: this.answers.publishToGithub
202
}
203
]);
204
}
205
}
206
```
207
208
### Advanced Git Operations
209
210
Complex git workflows and repository management.
211
212
**Usage Examples:**
213
214
```typescript
215
export default class MyGenerator extends Generator {
216
async initializing() {
217
// Check git status and working directory
218
if (await this.isGitRepo()) {
219
const status = await this.simpleGit.status();
220
221
if (!status.isClean()) {
222
this.log('Warning: Working directory is not clean');
223
this.log('Uncommitted changes may be overwritten');
224
225
const proceed = await this.prompt({
226
type: 'confirm',
227
name: 'continueWithDirtyRepo',
228
message: 'Continue anyway?',
229
default: false
230
});
231
232
if (!proceed.continueWithDirtyRepo) {
233
throw new Error('Aborted due to uncommitted changes');
234
}
235
}
236
}
237
}
238
239
async writing() {
240
// Create branch for generated code
241
if (this.answers.createBranch) {
242
const branchName = `generator/${this.answers.name}`;
243
244
try {
245
await this.simpleGit.checkoutLocalBranch(branchName);
246
this.log(`Created and switched to branch: ${branchName}`);
247
} catch (error) {
248
this.log(`Branch ${branchName} may already exist, switching to it`);
249
await this.simpleGit.checkout(branchName);
250
}
251
}
252
253
// Set up git hooks
254
if (this.answers.setupHooks) {
255
this.renderTemplate('pre-commit.ejs', '.git/hooks/pre-commit', {
256
runLint: this.answers.useLinting,
257
runTests: this.answers.useTesting
258
});
259
260
// Make hook executable
261
await this.spawn('chmod', ['+x', '.git/hooks/pre-commit']);
262
}
263
}
264
265
async end() {
266
// Tag the generated version
267
if (this.answers.createTag) {
268
const tagName = `v${this.packageJson.get('version')}`;
269
270
await this.simpleGit.addTag(tagName);
271
this.log(`Created tag: ${tagName}`);
272
}
273
274
// Generate changelog from commits
275
if (this.answers.generateChangelog) {
276
const log = await this.simpleGit.log(['--oneline', '--decorate']);
277
278
const changelog = log.all.map(commit =>
279
`- ${commit.message} (${commit.hash.substring(0, 7)})`
280
).join('\n');
281
282
this.writeDestination('CHANGELOG.md', `# Changelog\n\n${changelog}\n`);
283
}
284
}
285
}
286
```
287
288
### Git Configuration and Validation
289
290
Validate git setup and configure repository settings.
291
292
**Usage Examples:**
293
294
```typescript
295
export default class MyGenerator extends Generator {
296
async initializing() {
297
// Comprehensive git validation
298
await this.validateGitSetup();
299
}
300
301
async validateGitSetup() {
302
// Check if git is available
303
try {
304
await this.spawnCommand('git --version', { stdio: 'pipe' });
305
} catch (error) {
306
throw new Error('Git is not installed or not available in PATH');
307
}
308
309
// Check git configuration
310
const gitName = await this.git.name();
311
const gitEmail = await this.git.email();
312
313
if (!gitName) {
314
this.log('⚠️ Git user.name is not configured');
315
this.log('Run: git config --global user.name "Your Name"');
316
}
317
318
if (!gitEmail) {
319
this.log('⚠️ Git user.email is not configured');
320
this.log('Run: git config --global user.email "your.email@example.com"');
321
}
322
323
// Check if we're in a git repository
324
const isRepo = await this.isGitRepo();
325
if (!isRepo && this.answers.requireGitRepo) {
326
throw new Error('This generator requires a git repository. Run "git init" first.');
327
}
328
329
return { gitName, gitEmail, isRepo };
330
}
331
332
async configuring() {
333
// Configure repository-specific settings
334
if (this.answers.configureGit) {
335
// Set repository-specific configuration
336
await this.simpleGit.addConfig('core.autocrlf', 'input');
337
await this.simpleGit.addConfig('pull.rebase', 'false');
338
339
// Configure commit template
340
if (this.answers.useCommitTemplate) {
341
this.writeDestination('.gitmessage', `
342
# Title: Summary, imperative, start upper case, don't end with a period
343
# No more than 50 chars. #### 50 chars is here: #
344
345
# Remember blank line between title and body.
346
347
# Body: Explain *what* and *why* (not *how*). Include task ID (Jira issue).
348
# Wrap at 72 chars. ################################## which is here: #
349
350
351
# At the end: Include Co-authored-by for all contributors.
352
# Include at least one empty line before it. Format:
353
# Co-authored-by: name <user@users.noreply.github.com>
354
#
355
# How to Write a Git Commit Message:
356
# https://chris.beams.io/posts/git-commit/
357
#
358
# 1. Separate subject from body with a blank line
359
# 2. Limit the subject line to 50 characters
360
# 3. Capitalize the subject line
361
# 4. Do not end the subject line with a period
362
# 5. Use the imperative mood in the subject line
363
# 6. Wrap the body at 72 characters
364
# 7. Use the body to explain what and why vs. how
365
`.trim());
366
367
await this.simpleGit.addConfig('commit.template', '.gitmessage');
368
}
369
}
370
}
371
372
async isGitRepo(): Promise<boolean> {
373
try {
374
await this.simpleGit.status();
375
return true;
376
} catch (error) {
377
return false;
378
}
379
}
380
}
381
```
382
383
## Types
384
385
```typescript { .api }
386
interface GitUtil {
387
name(): Promise<string | undefined>;
388
email(): Promise<string | undefined>;
389
}
390
391
// Simple-git types (from simple-git package)
392
interface SimpleGit {
393
init(): Promise<void>;
394
status(): Promise<StatusResult>;
395
add(files: string | string[]): Promise<void>;
396
commit(message: string): Promise<CommitResult>;
397
push(remote?: string, branch?: string): Promise<void>;
398
addRemote(remoteName: string, remoteUrl: string): Promise<void>;
399
addConfig(key: string, value: string): Promise<void>;
400
getConfig(key: string): Promise<ConfigGetResult>;
401
addTag(tagName: string): Promise<void>;
402
log(options?: string[]): Promise<LogResult>;
403
checkout(branch: string): Promise<void>;
404
checkoutLocalBranch(branchName: string): Promise<void>;
405
}
406
```