git-chopstick-core 0.1.13 → 0.1.15

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.15] — 2026-06-13
4
+
5
+ ### Added
6
+ - **`includeBare` option for `getRepositories`**: Bare repository detection is now implemented. Uses a heuristic check (`HEAD` + `objects/` + `refs/` via `stat`) to detect bare repos without spawning git processes. Bare repos are added to results but their internal directories (`objects/`, `refs/`, etc.) are not traversed.
7
+ - **`getRepositoriesSummary(rootPath, options?)`**: Combines `getRepositories` + `getRepositorySummary` for an instant workspace overview. Silently omits bare repos and unborn HEADs from results.
8
+ - **Integration tests**: 4 new tests covering `getRepositoriesSummary` (2) and `includeBare` (2). Total: 60 tests.
9
+
10
+ ---
11
+
3
12
  ## [0.1.13] — 2026-06-13
4
13
 
5
14
  ### Added
@@ -192,6 +201,7 @@
192
201
 
193
202
  ---
194
203
 
204
+ [0.1.15]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.15
195
205
  [0.1.13]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.13
196
206
  [0.1.11]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.11
197
207
  [0.1.10]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.10
@@ -205,4 +215,4 @@
205
215
  [0.1.2]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.2
206
216
  [0.1.1]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.1
207
217
  [0.1.0]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.0
208
- [Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.13...HEAD
218
+ [Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.15...HEAD
@@ -1,4 +1,5 @@
1
1
  import { Repository } from '../models/repository.js';
2
+ import type { RepositorySummary } from './rev-parse.js';
2
3
  /**
3
4
  * Options for {@link getRepositories}.
4
5
  */
@@ -35,3 +36,21 @@ export interface GetRepositoriesOptions {
35
36
  * }
36
37
  */
37
38
  export declare function getRepositories(rootPath: string, options?: GetRepositoriesOptions): Promise<ReadonlyArray<Repository>>;
39
+ /**
40
+ * Discover git repositories in a directory tree and return a summary
41
+ * for each one.
42
+ *
43
+ * Combines {@link getRepositories} and {@link getRepositorySummary} into
44
+ * a single call — useful for getting an instant overview of a monorepo
45
+ * workspace without iterating over results manually.
46
+ *
47
+ * Repositories that fail to produce a summary (bare repos, detached HEAD
48
+ * with no commits, etc.) are silently omitted from the result.
49
+ *
50
+ * @example
51
+ * const summaries = await getRepositoriesSummary('/path/to/monorepo')
52
+ * for (const s of summaries) {
53
+ * console.log(`${s.path}: ${s.currentBranch ?? '(detached)'} @ ${s.head.slice(0, 7)}`)
54
+ * }
55
+ */
56
+ export declare function getRepositoriesSummary(rootPath: string, options?: GetRepositoriesOptions): Promise<ReadonlyArray<RepositorySummary>>;
@@ -2,6 +2,7 @@ import { opendir, stat } from 'fs/promises';
2
2
  import { join, resolve } from 'path';
3
3
  import { Repository } from '../models/repository.js';
4
4
  import { directoryExists } from '../lib/directory-exists.js';
5
+ import { getRepositorySummary } from './rev-parse.js';
5
6
  const defaultSkipDirs = new Set([
6
7
  'node_modules',
7
8
  '.git',
@@ -35,10 +36,6 @@ const defaultSkipDirs = new Set([
35
36
  */
36
37
  export async function getRepositories(rootPath, options = {}) {
37
38
  const { depth = 5, skipDirs = defaultSkipDirs, includeBare = false, } = options;
38
- if (includeBare) {
39
- throw new Error('Bare repository detection is not yet implemented. ' +
40
- 'Set includeBare to false (default) or omit it.');
41
- }
42
39
  if (!(await directoryExists(rootPath))) {
43
40
  return [];
44
41
  }
@@ -58,6 +55,28 @@ export async function getRepositories(rootPath, options = {}) {
58
55
  return false;
59
56
  }
60
57
  }
58
+ /**
59
+ * Check if a directory looks like a bare git repository.
60
+ *
61
+ * A bare repo has the git internals (HEAD, objects, refs) directly in
62
+ * the directory rather than inside a `.git` subdirectory.
63
+ *
64
+ * This is a heuristic that checks for the essential bare-repo indicators
65
+ * without spawning a git process (which would be too slow for large trees).
66
+ */
67
+ async function isBareRepo(dirPath) {
68
+ try {
69
+ const [headStat, objectsStat, refsStat] = await Promise.all([
70
+ stat(join(dirPath, 'HEAD')),
71
+ stat(join(dirPath, 'objects')),
72
+ stat(join(dirPath, 'refs')),
73
+ ]);
74
+ return headStat.isFile() && objectsStat.isDirectory() && refsStat.isDirectory();
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
61
80
  /**
62
81
  * Recursively walk a directory tree looking for repos.
63
82
  */
@@ -73,13 +92,19 @@ export async function getRepositories(rootPath, options = {}) {
73
92
  return;
74
93
  }
75
94
  seen.add(resolved);
76
- // Check if this directory is itself a repo
95
+ // Check if this directory is itself a regular repo (has .git directory/file)
77
96
  if (await hasGitDir(resolved)) {
78
97
  repos.push(new Repository(resolved, repos.length));
79
98
  // Continue recursing — the parent repo may contain nested repos
80
99
  // (e.g. monorepo workspaces where the root is a repo AND packages
81
100
  // have their own .git directories)
82
101
  }
102
+ else if (includeBare && (await isBareRepo(resolved))) {
103
+ repos.push(new Repository(resolved, repos.length));
104
+ // Don't recurse into bare repos — the subdirectories are git internals
105
+ // (objects/, refs/, etc.) and not the working tree
106
+ return;
107
+ }
83
108
  // Recurse into subdirectories
84
109
  let dir;
85
110
  try {
@@ -121,4 +146,32 @@ export async function getRepositories(rootPath, options = {}) {
121
146
  await walk(resolvedRoot, 0);
122
147
  return repos;
123
148
  }
149
+ /**
150
+ * Discover git repositories in a directory tree and return a summary
151
+ * for each one.
152
+ *
153
+ * Combines {@link getRepositories} and {@link getRepositorySummary} into
154
+ * a single call — useful for getting an instant overview of a monorepo
155
+ * workspace without iterating over results manually.
156
+ *
157
+ * Repositories that fail to produce a summary (bare repos, detached HEAD
158
+ * with no commits, etc.) are silently omitted from the result.
159
+ *
160
+ * @example
161
+ * const summaries = await getRepositoriesSummary('/path/to/monorepo')
162
+ * for (const s of summaries) {
163
+ * console.log(`${s.path}: ${s.currentBranch ?? '(detached)'} @ ${s.head.slice(0, 7)}`)
164
+ * }
165
+ */
166
+ export async function getRepositoriesSummary(rootPath, options = {}) {
167
+ const repos = await getRepositories(rootPath, options);
168
+ const results = await Promise.allSettled(repos.map(r => getRepositorySummary(r.path)));
169
+ const summaries = [];
170
+ for (const result of results) {
171
+ if (result.status === 'fulfilled' && result.value !== null) {
172
+ summaries.push(result.value);
173
+ }
174
+ }
175
+ return summaries;
176
+ }
124
177
  //# sourceMappingURL=discover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/git/discover.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/git/discover.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-chopstick-core",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -4,7 +4,7 @@ import { execSync } from 'child_process'
4
4
  import { tmpdir } from 'os'
5
5
  import { join } from 'path'
6
6
  import {
7
- Repository, getRepositories, getStatus, getWorkingDirectoryChanges, getWorkingDirectoryChangesDetailed, fileChangeSummaryToWorkingDirectoryFile, DiffSelectionType, getCommits, getBranches,
7
+ Repository, getRepositories, getRepositoriesSummary, getStatus, getWorkingDirectoryChanges, getWorkingDirectoryChangesDetailed, fileChangeSummaryToWorkingDirectoryFile, DiffSelectionType, getCommits, getBranches,
8
8
  createCommit, createBranch, deleteLocalBranch, renameBranch,
9
9
  getCurrentBranch, getRepositoryType, getRepositorySummary,
10
10
  getRemoteUrl, getRemotesFromPath, getAllTags,
@@ -610,6 +610,90 @@ describe('getRepositories', () => {
610
610
  expect(repos).toEqual([])
611
611
  execSync(`rm -rf ${nonRepoPath}`)
612
612
  })
613
+
614
+ it('detects bare repos when includeBare is true', async () => {
615
+ const root = mkdtempSync(join(tmpdir(), 'gcctest-bare-'))
616
+ execSync(`git init ${root}`, { stdio: 'pipe' })
617
+ execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
618
+
619
+ // Create a bare repo inside the tree
620
+ const bare = join(root, 'shared.git')
621
+ execSync(`mkdir -p ${bare}`)
622
+ execSync(`git init --bare ${bare}`, { stdio: 'pipe' })
623
+
624
+ // Without includeBare, should not find the bare repo
625
+ const reposWithout = await getRepositories(root)
626
+ expect(reposWithout.length).toBe(1)
627
+ expect(reposWithout[0].path).toBe(root)
628
+
629
+ // With includeBare, should find both
630
+ const reposWith = await getRepositories(root, { includeBare: true })
631
+ expect(reposWith.length).toBe(2)
632
+
633
+ const bareFound = reposWith.find(r => r.path === bare)
634
+ expect(bareFound).toBeTruthy()
635
+
636
+ execSync(`rm -rf ${root}`)
637
+ })
638
+
639
+ it('does not recurse into bare repo internals', async () => {
640
+ const root = mkdtempSync(join(tmpdir(), 'gcctest-bare-internals-'))
641
+ execSync(`git init ${root}`, { stdio: 'pipe' })
642
+ execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
643
+
644
+ const bare = join(root, 'project.git')
645
+ execSync(`mkdir -p ${bare}`)
646
+ execSync(`git init --bare ${bare}`, { stdio: 'pipe' })
647
+
648
+ const repos = await getRepositories(root, { includeBare: true })
649
+ // Should only be 2: the root repo and the bare repo. Subdirs of the bare
650
+ // repo like objects/, refs/ should NOT be discovered as separate repos.
651
+ expect(repos.length).toBe(2)
652
+
653
+ execSync(`rm -rf ${root}`)
654
+ })
655
+ })
656
+
657
+ describe('getRepositoriesSummary', () => {
658
+ it('returns summaries for all repos in a monorepo', async () => {
659
+ const root = mkdtempSync(join(tmpdir(), 'gcctest-summaries-'))
660
+ execSync(`git init ${root}`, { stdio: 'pipe' })
661
+ execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
662
+
663
+ const pkg = join(root, 'packages', 'pkg-a')
664
+ execSync(`mkdir -p ${pkg}`)
665
+ execSync(`git init ${pkg}`, { stdio: 'pipe' })
666
+ execSync(`git -C ${pkg} commit --allow-empty -m 'init pkg-a'`, { stdio: 'pipe' })
667
+
668
+ const summaries = await getRepositoriesSummary(root)
669
+ expect(summaries.length).toBe(2)
670
+
671
+ for (const s of summaries) {
672
+ expect(s.path).toBeTruthy()
673
+ expect(s.head).toMatch(/^[a-f0-9]{40}$/)
674
+ expect(s.currentBranch).toBe('main')
675
+ }
676
+
677
+ execSync(`rm -rf ${root}`)
678
+ })
679
+
680
+ it('skips repos that fail to produce a summary (bare repos)', async () => {
681
+ const root = mkdtempSync(join(tmpdir(), 'gcctest-bare-summary-'))
682
+ execSync(`git init ${root}`, { stdio: 'pipe' })
683
+ execSync(`git -C ${root} commit --allow-empty -m 'init'`, { stdio: 'pipe' })
684
+
685
+ // Create a bare repo inside the tree
686
+ const bare = join(root, 'bare-repo')
687
+ execSync(`mkdir -p ${bare}`)
688
+ execSync(`git init --bare ${bare}`, { stdio: 'pipe' })
689
+
690
+ const summaries = await getRepositoriesSummary(root)
691
+ // The bare repo should be silently skipped
692
+ expect(summaries.length).toBe(1)
693
+ expect(summaries[0].path).toBe(root)
694
+
695
+ execSync(`rm -rf ${root}`)
696
+ })
613
697
  })
614
698
 
615
699
  describe('addRemote', () => {