From 4fce1c9f9f4acdf0b2de1cfd95adc31956777fe1 Mon Sep 17 00:00:00 2001 From: Nelson Pires Date: Mon, 16 Feb 2026 12:42:32 -0300 Subject: [PATCH] refactor(server): consolidate git utilities into dedicated module with documentation (#435) * refactor:move_git_service_module_to_lib_git * refactor:move_git_credentials_module_to_lib_git * refactor:move_git_identity_storage_module_to_lib_git * refactor:add_git_domain_entrypoint_reexports * refactor:update_server_git_imports_to_domain_entrypoint * refactor:update_github_repo_git_import_to_domain_entrypoint * chore:remove_legacy_git_service_module_path * chore:remove_legacy_git_credentials_module_path * chore:remove_legacy_git_identity_storage_module_path * docs:add_git_module_documentation_in_domain_folder * docs:add_git_module_to_agents_documentation_map --- AGENTS.md | 4 + packages/web/server/index.js | 16 +- packages/web/server/lib/git/DOCUMENTATION.md | 145 ++++++++++++++++++ .../credentials.js} | 0 .../identity-storage.js} | 0 packages/web/server/lib/git/index.js | 6 + .../lib/{git-service.js => git/service.js} | 0 packages/web/server/lib/github-repo.js | 2 +- 8 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 packages/web/server/lib/git/DOCUMENTATION.md rename packages/web/server/lib/{git-credentials.js => git/credentials.js} (100%) rename packages/web/server/lib/{git-identity-storage.js => git/identity-storage.js} (100%) create mode 100644 packages/web/server/lib/git/index.js rename packages/web/server/lib/{git-service.js => git/service.js} (100%) diff --git a/AGENTS.md b/AGENTS.md index 2e820fc1..872e27b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,10 @@ Server-side integration modules used by API routes and runtime services. Quota provider registry, dispatch, and provider integrations for usage endpoints. - Module docs: `packages/web/server/lib/quota/DOCUMENTATION.md` +##### git +Git repository operations for the web server runtime. +- Module docs: `packages/web/server/lib/git/DOCUMENTATION.md` + ## Build / dev commands (verified) All scripts are in `package.json`. - Validate: `bun run type-check`, `bun run lint` diff --git a/packages/web/server/index.js b/packages/web/server/index.js index da56c951..95d0f50a 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -277,7 +277,7 @@ const resolveWorkspacePathFromWorktrees = async (targetPath, baseDirectory) => { const resolvedBase = path.resolve(baseDirectory || os.homedir()); try { - const { getWorktrees } = await import('./lib/git-service.js'); + const { getWorktrees } = await import('./lib/git/index.js'); const worktrees = await getWorktrees(resolvedBase); for (const worktree of worktrees) { @@ -6712,7 +6712,7 @@ async function main(options = {}) { const { scanSkillsRepository } = await import('./lib/skills-catalog/scan.js'); const { installSkillsFromRepository } = await import('./lib/skills-catalog/install.js'); const { scanClawdHubPage, installSkillsFromClawdHub, isClawdHubSource } = await import('./lib/skills-catalog/clawdhub/index.js'); - const { getProfiles, getProfile } = await import('./lib/git-identity-storage.js'); + const { getProfiles, getProfile } = await import('./lib/git/index.js'); const listGitIdentitiesForResponse = () => { try { @@ -7491,7 +7491,7 @@ async function main(options = {}) { let headOwnerForSearch = null; // First, check the branch's tracking info to see which remote it's on - const { getStatus } = await import('./lib/git-service.js'); + const { getStatus } = await import('./lib/git/index.js'); const status = await getStatus(directory).catch(() => null); if (status?.tracking) { const trackingRemote = status.tracking.split('/')[0]; @@ -7744,7 +7744,7 @@ async function main(options = {}) { // Determine the source remote for the head branch // Priority: 1) explicit headRemote, 2) tracking branch remote, 3) 'origin' if targeting non-origin let sourceRemote = headRemote; - const { getStatus, getRemotes } = await import('./lib/git-service.js'); + const { getStatus, getRemotes } = await import('./lib/git/index.js'); // If no explicit headRemote, check the branch's tracking info if (!sourceRemote) { @@ -8744,11 +8744,7 @@ async function main(options = {}) { let gitLibraries = null; const getGitLibraries = async () => { if (!gitLibraries) { - const [storage, service] = await Promise.all([ - import('./lib/git-identity-storage.js'), - import('./lib/git-service.js') - ]); - gitLibraries = { ...storage, ...service }; + gitLibraries = await import('./lib/git/index.js'); } return gitLibraries; }; @@ -8813,7 +8809,7 @@ async function main(options = {}) { app.get('/api/git/discover-credentials', async (req, res) => { try { - const { discoverGitCredentials } = await import('./lib/git-credentials.js'); + const { discoverGitCredentials } = await import('./lib/git/index.js'); const credentials = discoverGitCredentials(); res.json(credentials); } catch (error) { diff --git a/packages/web/server/lib/git/DOCUMENTATION.md b/packages/web/server/lib/git/DOCUMENTATION.md new file mode 100644 index 00000000..c787163b --- /dev/null +++ b/packages/web/server/lib/git/DOCUMENTATION.md @@ -0,0 +1,145 @@ +# Git Module Documentation + +## Purpose +This module provides Git repository operations for the web server runtime, including repository management, branch/worktree operations, status/diff queries, commit handling, and merge/rebase workflows. + +## Entrypoints and structure +- `packages/web/server/lib/git/`: Git module directory containing all Git-related functionality. + - `index.js`: Public API entry point imported by `packages/web/server/index.js`. + - `service.js`: Core Git operations (repository, branch, worktree, commit, merge/rebase, status/diff, log). + - `credentials.js`: Git credentials management. + - `identity-storage.js`: Git identity (user.name, user.email) storage. + +## Public API + +The following functions are exported and used by the web server: + +### Repository Operations +- `isGitRepository(directory)`: Check if a directory is a Git repository. +- `getGlobalIdentity()`: Get global Git user.name, user.email, and core.sshCommand. +- `getCurrentIdentity(directory)`: Get local Git identity (fallback to global if not set locally). +- `hasLocalIdentity(directory)`: Check if local Git identity is configured. +- `setLocalIdentity(directory, profile)`: Set local Git identity (userName, userEmail, authType, sshKey/host). +- `getRemoteUrl(directory, remoteName)`: Get URL for a specific remote. + +### Status and Diff Operations +- `getStatus(directory)`: Get comprehensive Git status including current branch, tracking, ahead/behind, file changes, diff stats, merge/rebase state. +- `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree. +- `getRangeDiff(directory, { base, head, path, contextLines })`: Get diff between two refs. +- `getRangeFiles(directory, { base, head })`: Get list of changed files between two refs. +- `getFileDiff(directory, { path, staged })`: Get original and modified file contents for a single file (handles images as data URLs). +- `collectDiffs(directory, files)`: Collect diff output for multiple files. +- `revertFile(directory, filePath)`: Revert a file to HEAD state. + +### Branch Operations +- `getBranches(directory)`: Get list of local and remote branches (filtered to active remote branches). +- `createBranch(directory, branchName, options)`: Create and checkout a new branch. +- `checkoutBranch(directory, branchName)`: Checkout an existing branch. +- `deleteBranch(directory, branch, options)`: Delete a branch (supports force flag). +- `renameBranch(directory, oldName, newName)`: Rename a branch and preserve upstream tracking. +- `getRemotes(directory)`: Get list of configured remotes. + +### Worktree Operations +- `getWorktrees(directory)`: List all git worktrees for a repository. +- `validateWorktreeCreate(directory, input)`: Validate worktree creation parameters (mode, branchName, startRef, upstream config). +- `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup). +- `removeWorktree(directory, input)`: Remove a worktree (optionally delete local branch). +- `isLinkedWorktree(directory)`: Check if directory is a linked worktree (not primary). + +### Commit and Remote Operations +- `commit(directory, message, options)`: Create a commit (supports addAll or specific files). +- `pull(directory, options)`: Pull changes from remote. +- `push(directory, options)`: Push changes to remote (auto-sets upstream if needed). +- `fetch(directory, options)`: Fetch changes from remote. +- `deleteRemoteBranch(directory, options)`: Delete a remote branch. + +### Log Operations +- `getLog(directory, options)`: Get commit history with stats (supports maxCount, from, to, file filters). +- `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit. + +### Merge and Rebase Operations +- `rebase(directory, options)`: Start a rebase onto a target branch. +- `abortRebase(directory)`: Abort an in-progress rebase. +- `continueRebase(directory)`: Continue a rebase after conflict resolution. +- `merge(directory, options)`: Merge a branch into current branch. +- `abortMerge(directory)`: Abort an in-progress merge. +- `continueMerge(directory)`: Continue a merge after conflict resolution. +- `getConflictDetails(directory)`: Get detailed conflict information including operation type, unmerged files, and diff. + +### Stash Operations +- `stash(directory, options)`: Stash changes (supports message and includeUntracked options). +- `stashPop(directory)`: Pop and apply the most recent stash. + +## Internal Helpers + +The following functions are internal helpers used by exported functions: +- `buildSshCommand(sshKeyPath)`: Build SSH command string for git config. +- `buildGitEnv()`: Build Git environment with SSH_AUTH_SOCK resolution. +- `createGit(directory)`: Create simple-git instance with environment. +- `normalizeDirectoryPath(value)`: Normalize directory paths (supports ~ expansion). +- `cleanBranchName(branch)`: Remove refs/heads/ or refs/ prefixes. +- `parseWorktreePorcelain(raw)`: Parse `git worktree list --porcelain` output. +- `resolveWorktreeProjectContext(directory)`: Resolve project context (projectID, primaryWorktree, worktreeRoot). +- `resolveCandidateDirectory(...)`: Generate unique worktree directory candidates. +- `resolveBranchForExistingMode(...)`: Resolve branch for existing-mode worktree creation. +- `applyUpstreamConfiguration(...)`: Set upstream tracking for new branches. +- And various other internal helpers for Git command execution and parsing. + +## Response Contracts + +### Status Response +- `current`: Current branch name. +- `tracking`: Upstream branch (e.g., 'origin/main'). +- `ahead`: Number of commits ahead of upstream. +- `behind`: Number of commits behind upstream. +- `files`: Array of file objects with `path`, `index`, `working_dir` status codes. +- `isClean`: Boolean indicating if working tree is clean. +- `diffStats`: Object mapping file paths to `{ insertions, deletions }`. +- `mergeInProgress`: Object with `{ head, message }` if merge in progress. +- `rebaseInProgress`: Object with `{ headName, onto }` if rebase in progress. + +### Worktree Create/Remove Response +- `head`: HEAD commit SHA. +- `name`: Worktree name. +- `branch`: Local branch name. +- `path`: Absolute path to worktree directory. + +### Log Response +- `all`: Array of commit objects with hash, date, message, author info, stats. +- `latest`: Latest commit object or null. +- `total`: Total number of commits. + +## Notes for Contributors + +### Adding a New Git Operation +1. Add the function to `packages/web/server/lib/git/service.js`. +2. Export the function if it's part of the public API. +3. Use `createGit(directory)` to get a simple-git instance with the correct environment. +4. Use `runGitCommand(cwd, args)` for direct git command execution with better error handling. +5. Use `runGitCommandOrThrow(cwd, args, fallbackMessage)` for commands that must succeed. +6. Return consistent error messages; use `parseGitErrorText(error)` to extract meaningful git errors. +7. Update this file with the new function in the appropriate API section. + +### SSH Key Handling +- SSH keys are escaped and validated via `escapeSshKeyPath` to prevent command injection. +- On Windows, paths are converted to MSYS format (`C:/path` → `/c/path`). +- SSH_AUTH_SOCK is automatically resolved via `resolveSshAuthSock` (checks GPG agent, gpgconf). + +### Worktree Naming +- Worktree names are slugified via `slugWorktreeName`. +- Random names use adjectives/nouns from `OPENCODE_ADJECTIVES` and `OPENCODE_NOUNS` lists. +- Branches created for new worktrees use `openchamber/` pattern. + +### Cross-Platform Considerations +- Use `normalizeDirectoryPath` for all directory inputs to handle `~` and path separators. +- Use `canonicalPath` for path comparisons to handle case-insensitive filesystems (Windows). +- Windows Git commands use MSYS/MinGW paths; avoid direct Windows paths in git commands. + +### Error Handling +- All exported functions should throw errors with descriptive messages. +- Use `console.error` for logging Git operation failures. +- Return structured objects for operations that need partial success reporting (e.g., merge/rebase conflicts). + +### Testing +- Run `bun run type-check`, `bun run lint`, and `bun run build` before finalizing changes. +- Consider edge cases: non-Git directories, missing remotes, conflict states, concurrent worktree operations. diff --git a/packages/web/server/lib/git-credentials.js b/packages/web/server/lib/git/credentials.js similarity index 100% rename from packages/web/server/lib/git-credentials.js rename to packages/web/server/lib/git/credentials.js diff --git a/packages/web/server/lib/git-identity-storage.js b/packages/web/server/lib/git/identity-storage.js similarity index 100% rename from packages/web/server/lib/git-identity-storage.js rename to packages/web/server/lib/git/identity-storage.js diff --git a/packages/web/server/lib/git/index.js b/packages/web/server/lib/git/index.js new file mode 100644 index 00000000..285092c7 --- /dev/null +++ b/packages/web/server/lib/git/index.js @@ -0,0 +1,6 @@ +// Git library public entrypoint +// Re-exports all Git operations, credentials, and identity storage functions + +export * from './service.js'; +export * from './credentials.js'; +export * from './identity-storage.js'; diff --git a/packages/web/server/lib/git-service.js b/packages/web/server/lib/git/service.js similarity index 100% rename from packages/web/server/lib/git-service.js rename to packages/web/server/lib/git/service.js diff --git a/packages/web/server/lib/github-repo.js b/packages/web/server/lib/github-repo.js index 4ca99ead..c5a1b603 100644 --- a/packages/web/server/lib/github-repo.js +++ b/packages/web/server/lib/github-repo.js @@ -1,4 +1,4 @@ -import { getRemoteUrl } from './git-service.js'; +import { getRemoteUrl } from './git/index.js'; export const parseGitHubRemoteUrl = (raw) => { if (typeof raw !== 'string') {