git-chopstick-core 0.1.10 → 0.1.13
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/CHANGELOG.md +40 -1
- package/README.md +9 -8
- package/dist/git/discover.d.ts +37 -0
- package/dist/git/discover.js +124 -0
- package/dist/git/discover.js.map +1 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/git/index.js +2 -0
- package/dist/git/index.js.map +1 -1
- package/dist/git/log.d.ts +17 -1
- package/dist/git/log.js +17 -0
- package/dist/git/log.js.map +1 -1
- package/dist/git/show.d.ts +11 -0
- package/dist/git/show.js +17 -0
- package/dist/git/show.js.map +1 -1
- package/dist/git/stash.d.ts +8 -2
- package/dist/git/stash.js +12 -6
- package/dist/git/stash.js.map +1 -1
- package/dist/git/status.d.ts +83 -1
- package/dist/git/status.js +137 -0
- package/dist/git/status.js.map +1 -1
- package/dist/git/tag.d.ts +12 -0
- package/dist/git/tag.js +23 -0
- package/dist/git/tag.js.map +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +1 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/status.d.ts +6 -0
- package/dist/models/status.js +23 -0
- package/dist/models/status.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/integration.test.ts +374 -2
|
@@ -4,14 +4,18 @@ import { execSync } from 'child_process'
|
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
import { join } from 'path'
|
|
6
6
|
import {
|
|
7
|
-
Repository, getStatus, getCommits, getBranches,
|
|
7
|
+
Repository, getRepositories, getStatus, getWorkingDirectoryChanges, getWorkingDirectoryChangesDetailed, fileChangeSummaryToWorkingDirectoryFile, DiffSelectionType, getCommits, getBranches,
|
|
8
8
|
createCommit, createBranch, deleteLocalBranch, renameBranch,
|
|
9
9
|
getCurrentBranch, getRepositoryType, getRepositorySummary,
|
|
10
10
|
getRemoteUrl, getRemotesFromPath, getAllTags,
|
|
11
11
|
addRemote, removeRemote, setRemoteURL,
|
|
12
12
|
merge, MergeResult,
|
|
13
13
|
rebase, RebaseResult,
|
|
14
|
-
getStashes,
|
|
14
|
+
getStashes, getStashesByPath,
|
|
15
|
+
createDesktopStashEntry, popStashEntry,
|
|
16
|
+
getTags, getFileAtCommit,
|
|
17
|
+
getChangedFilesFlat, getChangedFiles,
|
|
18
|
+
appFileStatusToString, AppFileStatusKind,
|
|
15
19
|
} from '../index.js'
|
|
16
20
|
import {
|
|
17
21
|
setupFixtureRepo, cleanupFixtureRepo, git,
|
|
@@ -85,6 +89,116 @@ describe('getStatus', () => {
|
|
|
85
89
|
})
|
|
86
90
|
})
|
|
87
91
|
|
|
92
|
+
describe('getWorkingDirectoryChanges', () => {
|
|
93
|
+
it('returns flattened changes for a repo with modified files', async () => {
|
|
94
|
+
// Modify an existing file and create a new (untracked) one
|
|
95
|
+
writeFileSync(join(repoPath, 'README.md'), 'modified content')
|
|
96
|
+
writeFileSync(join(repoPath, 'new-file.txt'), 'new content')
|
|
97
|
+
|
|
98
|
+
const changes = await getWorkingDirectoryChanges(repo)
|
|
99
|
+
|
|
100
|
+
expect(changes.length).toBeGreaterThanOrEqual(2)
|
|
101
|
+
|
|
102
|
+
const modified = changes.find(c => c.path === 'README.md')
|
|
103
|
+
expect(modified).toBeTruthy()
|
|
104
|
+
expect(modified!.status).toBe('modified')
|
|
105
|
+
expect(modified!.oldPath).toBeUndefined()
|
|
106
|
+
|
|
107
|
+
const untracked = changes.find(c => c.path === 'new-file.txt')
|
|
108
|
+
expect(untracked).toBeTruthy()
|
|
109
|
+
// Unstaged new files show as 'untracked' (not 'added' — that requires staging)
|
|
110
|
+
expect(untracked!.status).toBe('untracked')
|
|
111
|
+
|
|
112
|
+
// Clean up
|
|
113
|
+
git(repoPath, 'checkout -- README.md')
|
|
114
|
+
execSync(`rm -f ${join(repoPath, 'new-file.txt')}`)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('throws for a non-repo path', async () => {
|
|
118
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
119
|
+
await expect(
|
|
120
|
+
getWorkingDirectoryChanges(new Repository(nonRepoPath))
|
|
121
|
+
).rejects.toThrow()
|
|
122
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('includes oldPath for renamed files', async () => {
|
|
126
|
+
// Use an existing tracked file to avoid creating a commit
|
|
127
|
+
git(repoPath, 'mv README.md readme-backup.md')
|
|
128
|
+
|
|
129
|
+
const changes = await getWorkingDirectoryChanges(repo)
|
|
130
|
+
const renamed = changes.find(c => c.path === 'readme-backup.md')
|
|
131
|
+
expect(renamed).toBeTruthy()
|
|
132
|
+
expect(renamed!.status).toBe('renamed')
|
|
133
|
+
expect(renamed!.oldPath).toBe('README.md')
|
|
134
|
+
|
|
135
|
+
// Clean up: reverse the rename
|
|
136
|
+
git(repoPath, 'mv readme-backup.md README.md')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('getWorkingDirectoryChangesDetailed', () => {
|
|
141
|
+
it('includes selectionType and selection for each file', async () => {
|
|
142
|
+
// Modify an existing file
|
|
143
|
+
writeFileSync(join(repoPath, 'README.md'), 'detailed test')
|
|
144
|
+
|
|
145
|
+
const changes = await getWorkingDirectoryChangesDetailed(repo)
|
|
146
|
+
|
|
147
|
+
expect(changes.length).toBeGreaterThanOrEqual(1)
|
|
148
|
+
|
|
149
|
+
const modified = changes.find(c => c.path === 'README.md')
|
|
150
|
+
expect(modified).toBeTruthy()
|
|
151
|
+
expect(modified!.status).toBe('modified')
|
|
152
|
+
expect(modified!.selectionType).toBe(DiffSelectionType.All)
|
|
153
|
+
expect(modified!.selection).toBeTruthy()
|
|
154
|
+
// DiffSelection should report the file as fully selected
|
|
155
|
+
expect(modified!.selection.isSelected(0)).toBe(true)
|
|
156
|
+
|
|
157
|
+
// Clean up
|
|
158
|
+
git(repoPath, 'checkout -- README.md')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('round-trips through fileChangeSummaryToWorkingDirectoryFile', async () => {
|
|
162
|
+
// Create a file and stage it so it appears as 'added'
|
|
163
|
+
writeFileSync(join(repoPath, 'stage-test.txt'), 'stage me')
|
|
164
|
+
git(repoPath, 'add stage-test.txt')
|
|
165
|
+
|
|
166
|
+
const changes = await getWorkingDirectoryChangesDetailed(repo)
|
|
167
|
+
const added = changes.find(c => c.path === 'stage-test.txt')
|
|
168
|
+
expect(added).toBeTruthy()
|
|
169
|
+
expect(added!.status).toBe('added')
|
|
170
|
+
|
|
171
|
+
// Round-trip back to WorkingDirectoryFileChange
|
|
172
|
+
const wdfc = fileChangeSummaryToWorkingDirectoryFile(added!)
|
|
173
|
+
expect(wdfc.path).toBe('stage-test.txt')
|
|
174
|
+
expect(wdfc.selection.getSelectionType()).toBe(DiffSelectionType.All)
|
|
175
|
+
// Verify the status was properly reconstructed
|
|
176
|
+
expect(wdfc.status.kind).toBe(AppFileStatusKind.New)
|
|
177
|
+
|
|
178
|
+
// Clean up: unstage and remove
|
|
179
|
+
git(repoPath, 'reset HEAD -- stage-test.txt')
|
|
180
|
+
execSync(`rm -f ${join(repoPath, 'stage-test.txt')}`)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('preserves oldPath through round-trip for renamed files', async () => {
|
|
184
|
+
git(repoPath, 'mv README.md readme-backup.md')
|
|
185
|
+
|
|
186
|
+
const changes = await getWorkingDirectoryChangesDetailed(repo)
|
|
187
|
+
const renamed = changes.find(c => c.path === 'readme-backup.md')
|
|
188
|
+
expect(renamed).toBeTruthy()
|
|
189
|
+
expect(renamed!.oldPath).toBe('README.md')
|
|
190
|
+
|
|
191
|
+
// Round-trip
|
|
192
|
+
const wdfc = fileChangeSummaryToWorkingDirectoryFile(renamed!)
|
|
193
|
+
expect(wdfc.path).toBe('readme-backup.md')
|
|
194
|
+
expect(wdfc.status.kind).toBe(AppFileStatusKind.Renamed)
|
|
195
|
+
expect((wdfc.status as any).oldPath).toBe('README.md')
|
|
196
|
+
|
|
197
|
+
// Clean up: reverse the rename
|
|
198
|
+
git(repoPath, 'mv readme-backup.md README.md')
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
88
202
|
describe('getCommits', () => {
|
|
89
203
|
it('returns commits in order (fixture has 6 commits reachable from main)', async () => {
|
|
90
204
|
const commits = await getCommits(repo, 'HEAD', 10)
|
|
@@ -160,6 +274,181 @@ describe('Tag operations', () => {
|
|
|
160
274
|
expect(tags.size).toBeGreaterThanOrEqual(1)
|
|
161
275
|
expect(tags.has('v0.1.0')).toBe(true)
|
|
162
276
|
})
|
|
277
|
+
|
|
278
|
+
it('getTags returns the same tags as getAllTags', async () => {
|
|
279
|
+
const tags = await getTags(repoPath)
|
|
280
|
+
expect(tags.length).toBeGreaterThanOrEqual(1)
|
|
281
|
+
|
|
282
|
+
const v010 = tags.find(t => t.name === 'v0.1.0')
|
|
283
|
+
expect(v010).toBeTruthy()
|
|
284
|
+
expect(v010!.sha).toMatch(/^[a-f0-9]{40}$/)
|
|
285
|
+
|
|
286
|
+
// Verify consistency with getAllTags
|
|
287
|
+
const allTags = await getAllTags(repo)
|
|
288
|
+
expect(tags.length).toBe(allTags.size)
|
|
289
|
+
for (const t of tags) {
|
|
290
|
+
expect(allTags.get(t.name)).toBe(t.sha)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('getTags returns empty array for a tag-less repo', async () => {
|
|
295
|
+
const noTagPath = mkdtempSync(join(tmpdir(), 'gcctest-notags-'))
|
|
296
|
+
execSync(`git init ${noTagPath}`, { stdio: 'pipe' })
|
|
297
|
+
execSync(
|
|
298
|
+
`git -C ${noTagPath} commit --allow-empty -m 'init'`,
|
|
299
|
+
{ stdio: 'pipe' }
|
|
300
|
+
)
|
|
301
|
+
const tags = await getTags(noTagPath)
|
|
302
|
+
expect(tags).toEqual([])
|
|
303
|
+
execSync(`rm -rf ${noTagPath}`)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('getTags returns empty array for a non-repo path', async () => {
|
|
307
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
308
|
+
const tags = await getTags(nonRepoPath)
|
|
309
|
+
expect(tags).toEqual([])
|
|
310
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('getFileAtCommit', () => {
|
|
315
|
+
it('reads README.md at HEAD', async () => {
|
|
316
|
+
const content = await getFileAtCommit(repoPath, 'HEAD', 'README.md')
|
|
317
|
+
expect(content).toContain('# Test Repo')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('reads a file at the initial commit', async () => {
|
|
321
|
+
const commits = await getCommits(repo, 'HEAD', 10)
|
|
322
|
+
const initialSha = commits[commits.length - 1].sha
|
|
323
|
+
const content = await getFileAtCommit(repoPath, initialSha, 'README.md')
|
|
324
|
+
expect(content).toContain('# Test Repo')
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('reads src/index.js at a commit where it exists', async () => {
|
|
328
|
+
// The second commit adds src/index.js with 'console.log("hi")'
|
|
329
|
+
const commits = await getCommits(repo, 'HEAD', 10)
|
|
330
|
+
// Second-to-last commit has the initial version
|
|
331
|
+
const secondCommit = commits[commits.length - 2].sha
|
|
332
|
+
const content = await getFileAtCommit(repoPath, secondCommit, 'src/index.js')
|
|
333
|
+
expect(content.trim()).toBe('console.log("hi")')
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('throws for a file that does not exist at the given commit', async () => {
|
|
337
|
+
await expect(
|
|
338
|
+
getFileAtCommit(repoPath, 'HEAD', 'nonexistent-file.txt')
|
|
339
|
+
).rejects.toThrow()
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('getChangedFilesFlat', () => {
|
|
344
|
+
it('returns flat file changes for the merge commit', async () => {
|
|
345
|
+
const commits = await getCommits(repo, 'HEAD', 10)
|
|
346
|
+
// The merge commit merged feature/one (which added feature-one.txt)
|
|
347
|
+
const mergeSha = commits[0].sha
|
|
348
|
+
const flat = await getChangedFilesFlat(repo, mergeSha)
|
|
349
|
+
|
|
350
|
+
expect(flat.length).toBeGreaterThanOrEqual(1)
|
|
351
|
+
|
|
352
|
+
// Each entry should have path and statusKind
|
|
353
|
+
for (const f of flat) {
|
|
354
|
+
expect(f).toHaveProperty('path')
|
|
355
|
+
expect(f).toHaveProperty('statusKind')
|
|
356
|
+
expect(Object.values(AppFileStatusKind)).toContain(f.statusKind)
|
|
357
|
+
// oldPath should only be present for Renamed/Copied files
|
|
358
|
+
if (f.statusKind === AppFileStatusKind.Renamed || f.statusKind === AppFileStatusKind.Copied) {
|
|
359
|
+
expect(f.oldPath).toBeTruthy()
|
|
360
|
+
} else {
|
|
361
|
+
expect(f.oldPath).toBeUndefined()
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('matches structure from getChangedFiles', async () => {
|
|
367
|
+
const commits = await getCommits(repo, 'HEAD', 10)
|
|
368
|
+
const mergeSha = commits[0].sha
|
|
369
|
+
|
|
370
|
+
const [flat, full] = await Promise.all([
|
|
371
|
+
getChangedFilesFlat(repo, mergeSha),
|
|
372
|
+
getChangedFiles(repo, mergeSha),
|
|
373
|
+
])
|
|
374
|
+
|
|
375
|
+
expect(flat.length).toBe(full.files.length)
|
|
376
|
+
|
|
377
|
+
for (let i = 0; i < flat.length; i++) {
|
|
378
|
+
expect(flat[i].path).toBe(full.files[i].path)
|
|
379
|
+
expect(flat[i].statusKind).toBe(full.files[i].status.kind)
|
|
380
|
+
|
|
381
|
+
if (
|
|
382
|
+
full.files[i].status.kind === AppFileStatusKind.Renamed ||
|
|
383
|
+
full.files[i].status.kind === AppFileStatusKind.Copied
|
|
384
|
+
) {
|
|
385
|
+
expect(flat[i].oldPath).toBe(full.files[i].status.oldPath)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('throws for an unborn HEAD', async () => {
|
|
391
|
+
const unbornPath = mkdtempSync(join(tmpdir(), 'gcctest-unborn-'))
|
|
392
|
+
execSync(`git init ${unbornPath}`, { stdio: 'pipe' })
|
|
393
|
+
const unbornRepo = new Repository(unbornPath, 1)
|
|
394
|
+
// Cannot get changed files for a commit that doesn't exist
|
|
395
|
+
await expect(
|
|
396
|
+
getChangedFilesFlat(unbornRepo, 'HEAD')
|
|
397
|
+
).rejects.toThrow()
|
|
398
|
+
execSync(`rm -rf ${unbornPath}`)
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
describe('getStashesByPath', () => {
|
|
403
|
+
it('returns empty stashes when no stash exists', async () => {
|
|
404
|
+
const result = await getStashesByPath(repoPath)
|
|
405
|
+
expect(result.desktopEntries).toEqual([])
|
|
406
|
+
expect(result.stashEntryCount).toBe(0)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('returns same result as getStashes after creating a stash', async () => {
|
|
410
|
+
// Create a working directory change
|
|
411
|
+
writeFileSync(join(repoPath, 'stash-by-path-test.txt'), 'stash me')
|
|
412
|
+
git(repoPath, 'add stash-by-path-test.txt')
|
|
413
|
+
|
|
414
|
+
await createDesktopStashEntry(repo, 'main', [], null)
|
|
415
|
+
|
|
416
|
+
const byPath = await getStashesByPath(repoPath)
|
|
417
|
+
const byRepo = await getStashes(repo)
|
|
418
|
+
|
|
419
|
+
expect(byPath.desktopEntries.length).toBe(byRepo.desktopEntries.length)
|
|
420
|
+
expect(byPath.stashEntryCount).toBe(byRepo.stashEntryCount)
|
|
421
|
+
|
|
422
|
+
// Verify the entry has expected fields
|
|
423
|
+
const entry = byPath.desktopEntries[0]
|
|
424
|
+
expect(entry.branchName).toBe('main')
|
|
425
|
+
expect(entry.stashSha).toMatch(/^[a-f0-9]{40}$/)
|
|
426
|
+
|
|
427
|
+
// Clean up: pop the stash, unstage, and remove the test file
|
|
428
|
+
await popStashEntry(repo, entry.stashSha)
|
|
429
|
+
git(repoPath, 'reset HEAD -- stash-by-path-test.txt')
|
|
430
|
+
execSync(`rm -f ${join(repoPath, 'stash-by-path-test.txt')}`)
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('returns empty for a non-repo path', async () => {
|
|
434
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
435
|
+
const result = await getStashesByPath(nonRepoPath)
|
|
436
|
+
expect(result.desktopEntries).toEqual([])
|
|
437
|
+
expect(result.stashEntryCount).toBe(0)
|
|
438
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
describe('appFileStatusToString', () => {
|
|
443
|
+
it('maps each status kind to a human-readable string', () => {
|
|
444
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.New })).toBe('Added')
|
|
445
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Modified })).toBe('Modified')
|
|
446
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Deleted })).toBe('Deleted')
|
|
447
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Renamed })).toBe('Renamed')
|
|
448
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Copied })).toBe('Copied')
|
|
449
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Conflicted })).toBe('Conflicted')
|
|
450
|
+
expect(appFileStatusToString({ kind: AppFileStatusKind.Untracked })).toBe('Untracked')
|
|
451
|
+
})
|
|
163
452
|
})
|
|
164
453
|
|
|
165
454
|
describe('getRepositorySummary', () => {
|
|
@@ -240,6 +529,89 @@ describe('getRemotesFromPath', () => {
|
|
|
240
529
|
})
|
|
241
530
|
})
|
|
242
531
|
|
|
532
|
+
describe('getRepositories', () => {
|
|
533
|
+
it('discovers the root repo when scanning from repo path', async () => {
|
|
534
|
+
// Scan the fixture repo — should find it at the root
|
|
535
|
+
const repos = await getRepositories(repoPath)
|
|
536
|
+
expect(repos.length).toBeGreaterThanOrEqual(1)
|
|
537
|
+
|
|
538
|
+
const root = repos.find(r => r.path === repoPath)
|
|
539
|
+
expect(root).toBeTruthy()
|
|
540
|
+
// The fixture is created in a temp dir, so name will be the temp dir name
|
|
541
|
+
expect(root!.path).toBe(repoPath)
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it('finds nested repos in a monorepo-style layout', async () => {
|
|
545
|
+
const monorepoRoot = mkdtempSync(join(tmpdir(), 'gcctest-monorepo-'))
|
|
546
|
+
execSync(`git init ${monorepoRoot}`, { stdio: 'pipe' })
|
|
547
|
+
execSync(`git -C ${monorepoRoot} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
|
|
548
|
+
|
|
549
|
+
const pkgA = join(monorepoRoot, 'packages', 'pkg-a')
|
|
550
|
+
const pkgB = join(monorepoRoot, 'packages', 'pkg-b')
|
|
551
|
+
execSync(`mkdir -p ${pkgA} ${pkgB}`)
|
|
552
|
+
execSync(`git init ${pkgA}`, { stdio: 'pipe' })
|
|
553
|
+
execSync(`git -C ${pkgA} commit --allow-empty -m 'init pkg-a'`, { stdio: 'pipe' })
|
|
554
|
+
execSync(`git init ${pkgB}`, { stdio: 'pipe' })
|
|
555
|
+
execSync(`git -C ${pkgB} commit --allow-empty -m 'init pkg-b'`, { stdio: 'pipe' })
|
|
556
|
+
|
|
557
|
+
const repos = await getRepositories(monorepoRoot)
|
|
558
|
+
expect(repos.length).toBe(3)
|
|
559
|
+
|
|
560
|
+
const paths = repos.map(r => r.path).sort()
|
|
561
|
+
expect(paths).toContain(monorepoRoot)
|
|
562
|
+
expect(paths).toContain(pkgA)
|
|
563
|
+
expect(paths).toContain(pkgB)
|
|
564
|
+
|
|
565
|
+
execSync(`rm -rf ${monorepoRoot}`)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('skips node_modules directories', async () => {
|
|
569
|
+
const root = mkdtempSync(join(tmpdir(), 'gcctest-skips-'))
|
|
570
|
+
execSync(`git init ${root}`, { stdio: 'pipe' })
|
|
571
|
+
execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
|
|
572
|
+
|
|
573
|
+
const nm = join(root, 'node_modules', 'some-lib')
|
|
574
|
+
execSync(`mkdir -p ${nm}`)
|
|
575
|
+
execSync(`git init ${nm}`, { stdio: 'pipe' })
|
|
576
|
+
execSync(`git -C ${nm} commit --allow-empty -m 'dep'`, { stdio: 'pipe' })
|
|
577
|
+
|
|
578
|
+
const repos = await getRepositories(root)
|
|
579
|
+
expect(repos.length).toBe(1)
|
|
580
|
+
expect(repos[0].path).toBe(root)
|
|
581
|
+
|
|
582
|
+
execSync(`rm -rf ${root}`)
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('obeys the depth limit', async () => {
|
|
586
|
+
const root = mkdtempSync(join(tmpdir(), 'gcctest-depth-'))
|
|
587
|
+
execSync(`git init ${root}`, { stdio: 'pipe' })
|
|
588
|
+
execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
|
|
589
|
+
|
|
590
|
+
const deep = join(root, 'a', 'b', 'c')
|
|
591
|
+
execSync(`mkdir -p ${deep}`)
|
|
592
|
+
execSync(`git init ${deep}`, { stdio: 'pipe' })
|
|
593
|
+
execSync(`git -C ${deep} commit --allow-empty -m 'deep'`, { stdio: 'pipe' })
|
|
594
|
+
|
|
595
|
+
// With depth 2, should not find the deep repo
|
|
596
|
+
const repos = await getRepositories(root, { depth: 2 })
|
|
597
|
+
expect(repos.length).toBe(1)
|
|
598
|
+
expect(repos[0].path).toBe(root)
|
|
599
|
+
|
|
600
|
+
// With depth -1 (unlimited), should find the deep repo
|
|
601
|
+
const reposDeep = await getRepositories(root, { depth: -1 })
|
|
602
|
+
expect(reposDeep.length).toBe(2)
|
|
603
|
+
|
|
604
|
+
execSync(`rm -rf ${root}`)
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
it('returns empty array for a non-repo path', async () => {
|
|
608
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
609
|
+
const repos = await getRepositories(nonRepoPath)
|
|
610
|
+
expect(repos).toEqual([])
|
|
611
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
612
|
+
})
|
|
613
|
+
})
|
|
614
|
+
|
|
243
615
|
describe('addRemote', () => {
|
|
244
616
|
it('adds a new remote and returns it', async () => {
|
|
245
617
|
const remote = await addRemote(repo, 'upstream', 'https://github.com/upstream/repo.git')
|