gitnexus 1.6.5-rc.36 → 1.6.5-rc.37

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.
@@ -10,6 +10,7 @@ import { realpathSync } from 'fs';
10
10
  import path from 'path';
11
11
  import os from 'os';
12
12
  import { getInferredRepoName, resolveRepoIdentityRoot } from './git.js';
13
+ import { logger } from '../core/logger.js';
13
14
  /**
14
15
  * Normalise a repo path for registry comparison across platforms
15
16
  * (#664 review feedback from @evander-wang).
@@ -200,13 +201,42 @@ export const findRepo = async (startPath) => {
200
201
  }
201
202
  return null;
202
203
  };
204
+ function isReadOnlyFilesystemError(err) {
205
+ const code = err?.code;
206
+ return code === 'EROFS' || code === 'EACCES' || code === 'EPERM';
207
+ }
203
208
  /**
204
209
  * Keep generated index files ignored without modifying the user's root .gitignore.
205
210
  */
206
211
  export const ensureGitNexusIgnored = async (repoPath) => {
207
212
  const gitignorePath = path.join(getStoragePath(repoPath), '.gitignore');
208
- await fs.mkdir(path.dirname(gitignorePath), { recursive: true });
209
- await fs.writeFile(gitignorePath, '*\n', 'utf-8');
213
+ const desired = '*\n';
214
+ // Idempotent fast path: skip the write entirely when the file already has
215
+ // the expected content. Lets this run cleanly on read-only mounts (e.g.
216
+ // the documented Docker workflow with WORKSPACE_DIR bound :ro) when an
217
+ // earlier `analyze` already created the file. See issue #1549.
218
+ try {
219
+ if ((await fs.readFile(gitignorePath, 'utf-8')) === desired) {
220
+ await ensureGitInfoExclude(repoPath);
221
+ return;
222
+ }
223
+ }
224
+ catch (err) {
225
+ if (err?.code !== 'ENOENT')
226
+ throw err;
227
+ }
228
+ try {
229
+ await fs.mkdir(path.dirname(gitignorePath), { recursive: true });
230
+ await fs.writeFile(gitignorePath, desired, 'utf-8');
231
+ }
232
+ catch (err) {
233
+ if (isReadOnlyFilesystemError(err)) {
234
+ logger.warn({ path: gitignorePath, code: err.code }, 'GitNexus storage filesystem is not writable; skipping .gitnexus/.gitignore. Generated files may appear as untracked in this repo locally.');
235
+ }
236
+ else {
237
+ throw err;
238
+ }
239
+ }
210
240
  await ensureGitInfoExclude(repoPath);
211
241
  };
212
242
  const ensureGitInfoExclude = async (repoPath) => {
@@ -220,7 +250,6 @@ const ensureGitInfoExclude = async (repoPath) => {
220
250
  catch {
221
251
  return;
222
252
  }
223
- await fs.mkdir(path.dirname(excludePath), { recursive: true });
224
253
  let content = '';
225
254
  try {
226
255
  content = await fs.readFile(excludePath, 'utf-8');
@@ -236,7 +265,18 @@ const ensureGitInfoExclude = async (repoPath) => {
236
265
  if (excludes.includes(GITNEXUS_DIR) || excludes.includes(GITNEXUS_EXCLUDE_ENTRY))
237
266
  return;
238
267
  const separator = content.length === 0 || content.endsWith('\n') ? '' : '\n';
239
- await fs.writeFile(excludePath, `${content}${separator}${GITNEXUS_EXCLUDE_ENTRY}\n`, 'utf-8');
268
+ try {
269
+ await fs.mkdir(path.dirname(excludePath), { recursive: true });
270
+ await fs.writeFile(excludePath, `${content}${separator}${GITNEXUS_EXCLUDE_ENTRY}\n`, 'utf-8');
271
+ }
272
+ catch (err) {
273
+ if (isReadOnlyFilesystemError(err)) {
274
+ logger.warn({ path: excludePath, code: err.code }, 'GitNexus storage filesystem is not writable; skipping .git/info/exclude update. .gitnexus/ may appear as untracked in `git status` locally.');
275
+ }
276
+ else {
277
+ throw err;
278
+ }
279
+ }
240
280
  };
241
281
  // ─── Global Registry (~/.gitnexus/registry.json) ───────────────────────
242
282
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.36",
3
+ "version": "1.6.5-rc.37",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",