import * as plugins from './smartgit.plugins.js'; import { Smartgit } from './smartgit.classes.smartgit.js'; /** * class GitRepo allows access to git directories from node */ export class GitRepo { // STATIC /** * creates a new GitRepo Instance after cloning a project */ public static async fromCloningIntoDir( smartgitRefArg: Smartgit, fromArg: string, toArg: string ): Promise { const dirArg = plugins.path.resolve(toArg); await plugins.isomorphicGit.clone({ dir: toArg, fs: smartgitRefArg.envDeps.fs, http: smartgitRefArg.envDeps.http, url: fromArg, }); return new GitRepo(smartgitRefArg, toArg); } public static async fromCreatingRepoInDir( smartgitRefArg: Smartgit, dirArg: string ): Promise { dirArg = plugins.path.resolve(dirArg); await plugins.isomorphicGit.init({ dir: dirArg, fs: smartgitRefArg.envDeps.fs, }); return new GitRepo(smartgitRefArg, dirArg); } public static async fromOpeningRepoDir(smartgitRefArg: Smartgit, dirArg: string) { dirArg = plugins.path.resolve(dirArg); return new GitRepo(smartgitRefArg, dirArg); } // INSTANCE public smartgitRef: Smartgit; public repoDir: string; constructor(smartgitRefArg: Smartgit, repoDirArg: string) { this.smartgitRef = smartgitRefArg; this.repoDir = repoDirArg; } /** * lists remotes */ public async listRemotes(): Promise< { remote: string; url: string; }[] > { const remotes = await plugins.isomorphicGit.listRemotes({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, }); return remotes; } /** * ensures the existence of a remote within a repository * @param remoteNameArg * @param remoteUrlArg */ public async ensureRemote(remoteNameArg: string, remoteUrlArg: string): Promise { const remotes = await this.listRemotes(); const existingRemote = remotes.find((itemArg) => itemArg.remote === remoteNameArg); if (existingRemote) { if (existingRemote.url !== remoteUrlArg) { await plugins.isomorphicGit.deleteRemote({ remote: remoteNameArg, fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, }); } else { return; } } await plugins.isomorphicGit.addRemote({ remote: remoteNameArg, fs: this.smartgitRef.envDeps.fs, url: remoteUrlArg, }); } /** * gets the url for a specific remote */ public async getUrlForRemote(remoteName: string): Promise { const remotes = await this.listRemotes(); const existingRemote = remotes.find((remoteArg) => remoteArg.remote === remoteName); return existingRemote?.url; } public async pushBranchToRemote(branchName: string, remoteName: string) { await plugins.isomorphicGit.push({ fs: this.smartgitRef.envDeps.fs, http: this.smartgitRef.envDeps.http, ref: branchName, remote: remoteName, }); } /** * Get the diff of the current uncommitted changes while excluding specified files */ public async getUncommittedDiff(excludeFiles: string[] = []): Promise { const statusMatrix = await plugins.isomorphicGit.statusMatrix({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, }); const diffs: string[] = []; for (const row of statusMatrix) { const [filepath, head, workdir] = row; if (excludeFiles.includes(filepath)) { continue; // Skip excluded files } let headContent = ''; let workdirContent = ''; // Handle modified files if (head !== 0 && workdir !== 0 && head !== workdir) { headContent = await plugins.isomorphicGit .readBlob({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, oid: await plugins.isomorphicGit.resolveRef({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, ref: 'HEAD', }), filepath, }) .then((result) => new TextDecoder().decode(result.blob)); workdirContent = await this.smartgitRef.envDeps.fs.promises.readFile( plugins.path.join(this.repoDir, filepath), 'utf8' ); } // Handle added files if (head === 0 && workdir !== 0) { workdirContent = await this.smartgitRef.envDeps.fs.promises.readFile( plugins.path.join(this.repoDir, filepath), 'utf8' ); } // Handle deleted files if (head !== 0 && workdir === 0) { headContent = await plugins.isomorphicGit .readBlob({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, oid: await plugins.isomorphicGit.resolveRef({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, ref: 'HEAD', }), filepath, }) .then((result) => new TextDecoder().decode(result.blob)); } if (headContent || workdirContent) { const diff = plugins.diff.createTwoFilesPatch( filepath, filepath, headContent, workdirContent ); diffs.push(diff); } } return diffs; } /** * Get all commit messages with their dates and package.json version at each commit */ public async getAllCommitMessages(): Promise< { date: string; version: string; message: string }[] > { const commits = await plugins.isomorphicGit.log({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, }); const results = []; for (const commit of commits) { let version = 'unknown'; try { const packageJsonBlob = await plugins.isomorphicGit.readBlob({ fs: this.smartgitRef.envDeps.fs, dir: this.repoDir, oid: commit.oid, filepath: 'package.json', }); const packageJson = JSON.parse(new TextDecoder().decode(packageJsonBlob.blob)); version = packageJson.version; } catch (error) { // If package.json does not exist or any error occurs, leave version as 'unknown' } results.push({ date: new plugins.smarttime.ExtendedDate(commit.commit.committer.timestamp * 1000).exportToHyphedSortableDate(), version: version, message: commit.commit.message, }); } return results; } }