mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
Some checks are pending
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(server): resolve clone/upload/mapping roots from GITNEXUS_HOME CLONE_ROOT (git-clone.ts), UPLOAD_ROOT (upload-paths.ts), and the server-mapping file (embeddings/server-mapping.ts) computed their root from os.homedir() directly, ignoring GITNEXUS_HOME. The Docker image sets GITNEXUS_HOME=/data/gitnexus (the persistent volume that also holds the registry and indexes), so cloned/uploaded repos and their .gitnexus indexes instead landed in the container's ephemeral ~/.gitnexus and were lost on container recreation, while registry.json (which honors GITNEXUS_HOME) kept pointing at the now-dead path. That also defeated incremental re-analysis: a recreated container re-clones from scratch and full-rebuilds instead of git pull + incremental update. Source all three from the existing getGlobalDir() helper — the same GITNEXUS_HOME-aware primitive the registry and groups already use. Behavior is unchanged when GITNEXUS_HOME is unset (CLI / local installs): it falls back to ~/.gitnexus exactly as before. No signatures change and no UPLOAD_ROOT consumers are touched. Adds test/unit/gitnexus-home-roots.test.ts covering both the GITNEXUS_HOME-set and unset paths for all three roots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): make clone-root assertions GITNEXUS_HOME-aware; cover server-mapping fallback Addresses PR review (#2229): - git-clone.test.ts hardcoded path.join(os.homedir(), '.gitnexus', 'repos') for the clone root, so the "direct child of the clone root" and containment assertions failed when GITNEXUS_HOME was set in the ambient env (e.g. a CI runner). CLONE_ROOT now derives from getGlobalDir(); mirror that derivation via EXPECTED_CLONE_ROOT (GITNEXUS_HOME || ~/.gitnexus), computed at module load — the same point CLONE_ROOT is frozen — so the two always agree. - The GITNEXUS_HOME-unset fallback test covered clone + upload roots but not server-mapping (one of the three changed modules). Add a fallback case for readServerMapping. It redirects HOME/USERPROFILE to a tmp dir (os.homedir() honors them) so it exercises the real ~/.gitnexus fallback without writing into the developer's actual ~/.gitnexus/server-mapping.json. Both files pass with and without GITNEXUS_HOME set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): use mkdtemp for GITNEXUS_HOME path-root test temp dirs CodeQL js/insecure-temporary-file flagged the two writes that used a fixed os.tmpdir() name (gitnexus-home-roots-test, gitnexus-fallback-home): a co-located process could pre-create or symlink the predictable path before the test write lands. Switch both to fs.mkdtemp(), which atomically creates a uniquely-named directory — the canonical sanitizer this repo already uses (see core/group/storage.ts). Behavior is unchanged; tests still pass with and without GITNEXUS_HOME set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(embeddings): resolve server-mapping path for parity with clone/upload roots MAPPING_FILE now wraps path.resolve() like CLONE_ROOT (git-clone.ts) and UPLOAD_ROOT (upload-paths.ts), so a relative GITNEXUS_HOME yields an absolute path. No-op for the supported absolute-GITNEXUS_HOME config (Docker) and for readServerMapping's only caller (run-analyze.ts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): derive EXPECTED_CLONE_ROOT from getGlobalDir() to avoid drift The test mirror now imports getGlobalDir() and computes the expected clone root the same way production CLONE_ROOT does, instead of re-deriving the GITNEXUS_HOME || ~/.gitnexus fallback by hand. Byte-identical today; future-proof if getGlobalDir() grows a branch. (os import retained — still used by os.tmpdir().) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): rename shadowed savedHome in server-mapping fallback test The inner savedHome (saves process.env.HOME) shadowed the describe-scope savedHome (saves process.env.GITNEXUS_HOME). Renamed the inner one to savedProcessHome at its declaration and both restore sites in the finally block. Pure rename, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): scope customHome temp dir to the GITNEXUS_HOME-set cases beforeEach created a customHome temp dir for all five tests, but the two fallback cases never use it (one manages its own fakeHome, the other needs none). Moved the mkdtemp/rm into a nested describe('with GITNEXUS_HOME set') wrapping the three set-cases; the shared outer afterEach still restores GITNEXUS_HOME and resets modules for all five. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
64 lines
2.4 KiB
TypeScript
64 lines
2.4 KiB
TypeScript
/**
|
|
* Upload working-directory paths.
|
|
*
|
|
* Browser folder uploads are written into getGlobalDir()/uploads/{name}/ — a
|
|
* sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
|
|
* persists and behaves like a cloned one (the graph UI's /api/file reads its
|
|
* files after analysis, and DELETE /api/repo removes it). Staging happens in
|
|
* an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
|
|
* filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
|
|
* the exact Docker case this feature targets; see bridge-db.ts for the same
|
|
* anchored-staging pattern).
|
|
*/
|
|
|
|
import path from 'path';
|
|
import { sanitizeRepoName } from '../storage/git.js';
|
|
import { REPO_NAME_PATTERN } from './git-clone.js';
|
|
import { getGlobalDir } from '../storage/repo-manager.js';
|
|
|
|
/**
|
|
* Root directory for all uploaded repositories. Targets must resolve inside this.
|
|
*
|
|
* Sourced from getGlobalDir() so it honors GITNEXUS_HOME and stays a sibling of
|
|
* the clone root on the same (in Docker, persistent) volume. Falls back to
|
|
* ~/.gitnexus when the env var is unset.
|
|
*/
|
|
export const UPLOAD_ROOT = path.resolve(path.join(getGlobalDir(), 'uploads'));
|
|
|
|
/** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
|
|
export const STAGING_PREFIX = '.staging-';
|
|
|
|
/**
|
|
* Get the upload target directory for a repo name.
|
|
*
|
|
* Re-validates at the boundary (callers may derive the name from an untrusted
|
|
* manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
|
|
* emits for un-nameable inputs, names beginning with `.` (which would collide
|
|
* with the `.staging-` prefix), and anything outside the safe charset.
|
|
*/
|
|
export function getUploadDir(repoName: string): string {
|
|
if (
|
|
!repoName ||
|
|
repoName === '.' ||
|
|
repoName === '..' ||
|
|
repoName === 'unknown' ||
|
|
repoName.startsWith('.') ||
|
|
!REPO_NAME_PATTERN.test(repoName)
|
|
) {
|
|
throw new Error('Invalid repository name');
|
|
}
|
|
return path.join(UPLOAD_ROOT, repoName);
|
|
}
|
|
|
|
/**
|
|
* Derive a filesystem-safe upload directory name from the manifest's
|
|
* top-level folder. Returns null when the name is un-nameable (so the caller
|
|
* rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
|
|
*/
|
|
export function deriveUploadName(topLevelName: string): string | null {
|
|
const safe = sanitizeRepoName(topLevelName);
|
|
if (safe === 'unknown' || safe === '.' || safe === '..' || safe.startsWith('.')) {
|
|
return null;
|
|
}
|
|
return safe;
|
|
}
|