diff --git a/packages/backend/src/ai.json b/packages/backend/src/ai.json index 726f998c6..807668be7 100644 --- a/packages/backend/src/ai.json +++ b/packages/backend/src/ai.json @@ -5,6 +5,8 @@ "description" : "Chat bot application", "name" : "ChatBot", "repository": "https://github.com/redhat-et/locallm", + "branch": "main", + "sha": "bccd1c1", "icon": "natural-language-processing", "categories": [ "natural-language-processing" @@ -20,6 +22,8 @@ "description" : "Summarizer application", "name" : "Summarizer", "repository": "https://github.com/redhat-et/locallm", + "branch": "main", + "sha": "bccd1c1", "icon": "natural-language-processing", "categories": [ "natural-language-processing" diff --git a/packages/backend/src/managers/applicationManager.spec.ts b/packages/backend/src/managers/applicationManager.spec.ts index 2fa90e8d9..0363e4a45 100644 --- a/packages/backend/src/managers/applicationManager.spec.ts +++ b/packages/backend/src/managers/applicationManager.spec.ts @@ -165,6 +165,8 @@ describe('pullApplication', () => { name: 'Recipe 1', categories: [], description: '', + branch: 'branch', + sha: '000000', readme: '', repository: 'repo', }; @@ -184,10 +186,17 @@ describe('pullApplication', () => { }, }); await manager.pullApplication(recipe, model); + const gitCloneOptions = { + branch: 'branch', + repository: 'repo', + sha: '000000', + targetDirectory: '\\home\\user\\aistudio\\recipe1', + }; if (process.platform === 'win32') { - expect(cloneRepositoryMock).toHaveBeenNthCalledWith(1, 'repo', '\\home\\user\\aistudio\\recipe1'); + expect(cloneRepositoryMock).toHaveBeenNthCalledWith(1, gitCloneOptions); } else { - expect(cloneRepositoryMock).toHaveBeenNthCalledWith(1, 'repo', '/home/user/aistudio/recipe1'); + gitCloneOptions.targetDirectory = '/home/user/aistudio/recipe1'; + expect(cloneRepositoryMock).toHaveBeenNthCalledWith(1, gitCloneOptions); } expect(doDownloadModelWrapperSpy).toHaveBeenCalledOnce(); expect(mocks.builImageMock).toHaveBeenCalledOnce(); @@ -202,6 +211,8 @@ describe('pullApplication', () => { name: 'Recipe 1', categories: [], description: '', + branch: 'branch', + sha: '000000', readme: '', repository: 'repo', }; @@ -228,6 +239,8 @@ describe('pullApplication', () => { id: 'recipe1', name: 'Recipe 1', categories: [], + branch: 'branch', + sha: '000000', description: '', readme: '', repository: 'repo', @@ -257,6 +270,8 @@ describe('pullApplication', () => { name: 'Recipe 1', categories: [], description: '', + branch: 'branch', + sha: '000000', readme: '', repository: 'repo', }; @@ -296,8 +311,15 @@ describe('doCheckout', () => { {} as unknown as RecipeStatusRegistry, {} as unknown as ModelsManager, ); - await manager.doCheckout('repo', 'folder', taskUtils); - expect(cloneRepositoryMock).toBeCalledWith('repo', 'folder'); + const gitCloneOptions = { + branch: 'branch', + repository: 'repo', + sha: '000000', + targetDirectory: 'folder', + }; + await manager.doCheckout(gitCloneOptions, taskUtils); + + expect(cloneRepositoryMock).toBeCalledWith(gitCloneOptions); expect(setTaskMock).toHaveBeenLastCalledWith({ id: 'checkout', name: 'Checkout repository', @@ -323,7 +345,15 @@ describe('doCheckout', () => { {} as unknown as RecipeStatusRegistry, {} as unknown as ModelsManager, ); - await manager.doCheckout('repo', 'folder', taskUtils); + await manager.doCheckout( + { + repository: 'repo', + branch: 'branch', + sha: '000000', + targetDirectory: 'folder', + }, + taskUtils, + ); expect(mkdirSyncMock).not.toHaveBeenCalled(); expect(cloneRepositoryMock).not.toHaveBeenCalled(); expect(setTaskMock).toHaveBeenLastCalledWith({ diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index d7b919017..7288ffcda 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -17,7 +17,7 @@ ***********************************************************************/ import type { Recipe } from '@shared/src/models/IRecipe'; -import type { GitManager } from './gitManager'; +import type { GitCloneInfo, GitManager } from './gitManager'; import fs from 'fs'; import * as https from 'node:https'; import * as path from 'node:path'; @@ -88,7 +88,13 @@ export class ApplicationManager { const localFolder = path.join(this.appUserDirectory, recipe.id); // clone the recipe repository on the local folder - await this.doCheckout(recipe.repository, localFolder, taskUtil); + const gitCloneInfo: GitCloneInfo = { + repository: recipe.repository, + branch: recipe.branch, + sha: recipe.sha, + targetDirectory: localFolder, + }; + await this.doCheckout(gitCloneInfo, taskUtil); // load and parse the recipe configuration file and filter containers based on architecture, gpu accelerator // and backend (that define which model supports) @@ -536,7 +542,7 @@ export class ApplicationManager { }; } - async doCheckout(repository: string, localFolder: string, taskUtil: RecipeStatusUtils) { + async doCheckout(gitCloneInfo: GitCloneInfo, taskUtil: RecipeStatusUtils) { // Adding checkout task const checkoutTask: Task = { id: 'checkout', @@ -549,17 +555,17 @@ export class ApplicationManager { taskUtil.setTask(checkoutTask); // We might already have the repository cloned - if (fs.existsSync(localFolder) && fs.statSync(localFolder).isDirectory()) { + if (fs.existsSync(gitCloneInfo.targetDirectory) && fs.statSync(gitCloneInfo.targetDirectory).isDirectory()) { // Update checkout state checkoutTask.name = 'Checkout repository (cached).'; checkoutTask.state = 'success'; } else { // Create folder - fs.mkdirSync(localFolder, { recursive: true }); + fs.mkdirSync(gitCloneInfo.targetDirectory, { recursive: true }); // Clone the repository - console.log(`Cloning repository ${repository} in ${localFolder}.`); - await this.git.cloneRepository(repository, localFolder); + console.log(`Cloning repository ${gitCloneInfo.repository} in ${gitCloneInfo.targetDirectory}.`); + await this.git.cloneRepository(gitCloneInfo); // Update checkout state checkoutTask.state = 'success'; diff --git a/packages/backend/src/managers/gitManager.ts b/packages/backend/src/managers/gitManager.ts index 919e232ac..0a399a012 100644 --- a/packages/backend/src/managers/gitManager.ts +++ b/packages/backend/src/managers/gitManager.ts @@ -16,15 +16,20 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import simpleGit, { type SimpleGit } from 'simple-git'; +import simpleGit from 'simple-git'; -export class GitManager { - private readonly simpleGit: SimpleGit; - constructor() { - this.simpleGit = simpleGit(); - } +export interface GitCloneInfo { + repository: string; + branch: string; + sha: string; + targetDirectory: string; +} - async cloneRepository(repository: string, targetDirectory: string) { - return this.simpleGit.clone(repository, targetDirectory); +export class GitManager { + async cloneRepository(gitCloneInfo: GitCloneInfo) { + // clone repo + await simpleGit().clone(gitCloneInfo.repository, gitCloneInfo.targetDirectory, ['-b', gitCloneInfo.branch]); + // checkout to specific branch + await simpleGit(gitCloneInfo.targetDirectory).checkout([gitCloneInfo.sha]); } } diff --git a/packages/shared/src/models/IRecipe.ts b/packages/shared/src/models/IRecipe.ts index 7f60b8e75..c8e6be210 100644 --- a/packages/shared/src/models/IRecipe.ts +++ b/packages/shared/src/models/IRecipe.ts @@ -5,6 +5,8 @@ export interface Recipe { description: string; icon?: string; repository: string; + branch: string; + sha: string; readme: string; config?: string; models?: string[];