From 7ff984e47ff93e7289cc2ab9a004d6062ce1d154 Mon Sep 17 00:00:00 2001 From: BeSl Date: Sun, 9 Nov 2025 21:57:59 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=B4=D0=BB=D0=B8=D0=BD=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/company/commitet_jm/service/ones/OneRunner.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/company/commitet_jm/service/ones/OneRunner.kt b/src/main/kotlin/com/company/commitet_jm/service/ones/OneRunner.kt index abc8e93..eee3e12 100644 --- a/src/main/kotlin/com/company/commitet_jm/service/ones/OneRunner.kt +++ b/src/main/kotlin/com/company/commitet_jm/service/ones/OneRunner.kt @@ -45,8 +45,8 @@ class OneRunner(private val dataManager: DataManager "DESIGNER", "/DisableStartupDialogs", "/DumpExternalDataProcessorOrReportToFiles", - "${targetDir.path}${File.separator}", - "${inputFile.path}${File.separator}", + "\"${targetDir.path}\"", + "\"${inputFile.path}\"", "/Out", logFileName )) @@ -97,7 +97,7 @@ class OneRunner(private val dataManager: DataManager fun pathPlatform(basePath: String?, version: String?):String{ val osName = System.getProperty("os.name").lowercase() return if (osName.contains("windows")) { - "$basePath\\$version\\bin\\1cv8.exe" + "\"$basePath\\$version\\bin\\1cv8.exe\"" } else { "$basePath/$version/1cv8s" } From ad40f1c2839aff47b9799cc1d33c2d44c6b05423 Mon Sep 17 00:00:00 2001 From: BeSl Date: Tue, 11 Nov 2025 20:26:04 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commitet_jm/component/ShellExecutor.kt | 29 +- .../company/commitet_jm/service/GitWorker.kt | 364 ------------------ .../service/file/FileServiceImpl.kt | 6 +- .../commitet_jm/service/git/GitServiceImpl.kt | 307 +++++++-------- .../commitet_jm/sheduledJob/Committer.kt | 1 - .../view/commit/CommitDetailView.kt | 7 - src/main/resources/application.yml | 4 + 7 files changed, 185 insertions(+), 533 deletions(-) delete mode 100644 src/main/kotlin/com/company/commitet_jm/service/GitWorker.kt diff --git a/src/main/kotlin/com/company/commitet_jm/component/ShellExecutor.kt b/src/main/kotlin/com/company/commitet_jm/component/ShellExecutor.kt index 8c0bfed..5f2748e 100644 --- a/src/main/kotlin/com/company/commitet_jm/component/ShellExecutor.kt +++ b/src/main/kotlin/com/company/commitet_jm/component/ShellExecutor.kt @@ -7,13 +7,15 @@ import java.io.IOException import java.util.concurrent.TimeUnit @Component -class ShellExecutor(var workingDir: File = File("."), var timeout:Long = 1) { +class ShellExecutor(var workingDir: File = File("."), var timeout:Long = 5) { companion object { private val log = LoggerFactory.getLogger(ShellExecutor::class.java) } - fun executeCommand(command: List): String { + data class CommandResult(val exitCode: Int, val output: String, val error: String) + + fun executeCommandWithResult(command: List, customTimeout: Long = timeout): CommandResult { try { val process = ProcessBuilder(command.filterNotNull()) .directory(workingDir) @@ -22,23 +24,24 @@ class ShellExecutor(var workingDir: File = File("."), var timeout:Long = 1) { .start() // Используем ограничение по времени для предотвращения блокировки - val finished = process.waitFor(timeout, TimeUnit.MINUTES) + val finished = process.waitFor(customTimeout, TimeUnit.MINUTES) if (!finished) { process.destroyForcibly() - throw RuntimeException("Command timed out after $timeout minutes") + throw RuntimeException("Command timed out after $customTimeout minutes") } val output = process.inputStream.bufferedReader().use { it.readText() } val error = process.errorStream.bufferedReader().use { it.readText() } + val exitCode = process.exitValue() - if (process.exitValue() != 0) { - log.error("Command failed: ${command.filterNotNull().joinToString(" ")}\nError: $error") - throw RuntimeException("exec command failed: $error") + if (exitCode != 0) { + log.error("Command failed with exit code $exitCode: ${command.filterNotNull().joinToString(" ")}\nError: $error") + } else { + log.info("Command executed successfully: ${command.filterNotNull().joinToString(" ")}\nOutput: $output") } - log.info("Command executed: ${command.filterNotNull().joinToString(" ")}\nOutput: $output") - return output + return CommandResult(exitCode, output, error) } catch (e: IOException) { log.error("IO error executing command: ${e.message}") throw e @@ -48,4 +51,12 @@ class ShellExecutor(var workingDir: File = File("."), var timeout:Long = 1) { throw RuntimeException("Operation interrupted") } } + + fun executeCommand(command: List, customTimeout: Long = timeout): String { + val result = executeCommandWithResult(command, customTimeout) + if (result.exitCode != 0) { + throw RuntimeException("exec command failed: ${result.error}") + } + return result.output + } } \ No newline at end of file diff --git a/src/main/kotlin/com/company/commitet_jm/service/GitWorker.kt b/src/main/kotlin/com/company/commitet_jm/service/GitWorker.kt deleted file mode 100644 index c4a2ae7..0000000 --- a/src/main/kotlin/com/company/commitet_jm/service/GitWorker.kt +++ /dev/null @@ -1,364 +0,0 @@ -package com.company.commitet_jm.service - -import com.company.commitet_jm.component.ShellExecutor -import com.company.commitet_jm.entity.* -import com.company.commitet_jm.entity.TypesFiles.* -import com.company.commitet_jm.service.ones.OneRunner -import io.jmix.core.DataManager -import io.jmix.core.FileStorage -import io.jmix.core.FileStorageLocator -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Service -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.StandardCopyOption -import java.util.* - -@Service -class GitWorker( - private val dataManager: DataManager, - private val fileStorageLocator: FileStorageLocator, - private val ones: OneRunner, -) { - - companion object { - private val log = LoggerFactory.getLogger(GitWorker::class.java) - } - - fun cloneRepo(repoUrl:String, directoryPath: String, branch: String):Pair { - val executor = ShellExecutor(timeout = 7) - log.info("start clone repo $repoUrl") - validateGitUrl(repoUrl) - - - val dir = File(directoryPath) - - if (dir.exists() && dir.list()?.isNotEmpty() == true) { - throw IllegalArgumentException("Target directory must be empty") - } - - executor.executeCommand(listOf( - "git", "clone", - "--branch", branch, - "--single-branch", - repoUrl, - directoryPath - )) - log.info("end clone repo $repoUrl") - return Pair(true, "") - } - - fun createCommit() { - log.info("screateCommit from GitWorker") - val commitInfo = firstNewDataCommit() ?: return - - log.info("start createCommit ${commitInfo.taskNum}") - - val repoPath = commitInfo.project!!.localPath!! - val repoDir = commitInfo.project!!.localPath?.let { File(it) } - val remoteBranch = commitInfo.project!!.defaultBranch - val newBranch = "feature/${commitInfo.taskNum?.let { sanitizeGitBranchName(it) }}" - - try { - - if (repoDir != null) { - beforeCmdCommit(repoDir, remoteBranch!!,newBranch, commitInfo) - }else{ - throw RuntimeException("$repoDir not exist!!!") - } - - commitInfo.project?.platform?.let { saveFileCommit(repoPath, commitInfo.files, it) } - - afterCmdCommit(commitInfo, repoDir, newBranch) - - } catch (e: Exception) { - log.error("Error occurred while creating commit ${e.message}") - commitInfo.errorInfo = e.message - commitInfo.setStatus(StatusSheduler.ERROR) - dataManager.save(commitInfo) - } - - } - - private fun validateGitUrl(url: String) { - if (!url.matches(Regex("^(https?|git|ssh)://.*"))) { - throw IllegalArgumentException("Invalid Git URL format") - } - } - - private fun beforeCmdCommit(repoDir: File, remoteBranch: String, newBranch:String, commitInfo: Commit){ - if (!File(repoDir, ".git").exists()) { - throw IllegalArgumentException("Not a git repository") - } - val repoPath = commitInfo.project!!.localPath!! - val executor = ShellExecutor(workingDir = repoDir, timeout = 7) - try { - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - }catch (e: Exception){ - - if (e.message?.contains("index.lock") == true) { - log.info("Обнаружена блокировка Git. Удаление index.lock...") - - val lockFile = File("$repoDir/.git/index.lock") - if (lockFile.exists()) { - lockFile.delete() - log.info("Файл index.lock удалён. Повторная попытка...") - - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - } else { - throw IllegalStateException("Файл блокировки не найден, но ошибка возникла: ${e.message}") - } - } else if (e.message?.contains("would be overwritten by checkout") == true) { - - executor.executeCommand(listOf("git", "reset", "--hard")) - executor.executeCommand(listOf("git", "clean", "-fd")) - - } else { - throw e - } - } - - executor.executeCommand(listOf("git", "reset", "--hard", "origin/$remoteBranch")) - executor.executeCommand(listOf("git", "clean", "-fd")) - - commitInfo.id?.let { setStatusCommit(it, StatusSheduler.PROCESSED) } - - val remoteBranches = executor.executeCommand(listOf("git", "branch", "-a")) - - if (!remoteBranches.contains("remotes/origin/$remoteBranch")) { - throw IllegalStateException("Default branch does not exist") - } - - try { - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - executor.executeCommand(listOf("git", "fetch", "origin", remoteBranch)) - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - - // Создаем новую ветку - if (branchExists(repoPath, newBranch)) { - log.info("create new branch $newBranch") - executor.executeCommand(listOf("git", "checkout", newBranch)) - } else { - log.info("checkout branch $newBranch") - executor.executeCommand(listOf("git", "checkout", "-b", newBranch)) - } - } catch (e:Exception){ - log.error("beforeCmdCommit ${e.message}") - throw RuntimeException("Error cmd git ${e.message}") - } - } - - private fun afterCmdCommit(commitInfo: Commit, repoDir: File, newBranch: String) { - val executor = ShellExecutor(workingDir = repoDir, timeout = 7) - executor.executeCommand(listOf("git", "add", ".")) - - // 4. Создаем коммит от указанного пользователя - // Создаем временный файл с сообщением коммита - val commitMessage = commitInfo.description ?: "Default commit message" - val tempFile = File.createTempFile("commit-message", ".txt") - tempFile.writeText(commitMessage, Charsets.UTF_8) - - try { - executor.executeCommand( - listOf( - "git", - "-c", "user.name=${commitInfo.author!!.gitLogin}", - "-c", "user.email=${commitInfo.author!!.email}", - "commit", - "-F", tempFile.absolutePath - ) - ) - } finally { - // Удаляем временный файл - tempFile.delete() - } - - log.info("git push start") - executor.executeCommand(listOf("git", "push", "--force", "-u", "origin", newBranch)) - log.info("git push end") - commitInfo.hashCommit = executor.executeCommand(listOf("git", "rev-parse", "HEAD")) - - log.info("Successfully committed and pushed changes to branch $newBranch") - - commitInfo.urlBranch = "${commitInfo.project!!.urlRepo}/tree/$newBranch" - - commitInfo.setStatus(StatusSheduler.COMPLETE) - dataManager.save(commitInfo) - - } - - private fun saveFileCommit(baseDir: String, files: MutableList, platform: Platform) { - val executor = ShellExecutor(workingDir = File(baseDir), timeout = 7) - val filesToUnpack = mutableListOf>() - for (file in files) { - val content = file.data ?: continue - - // correctPath возвращает File, приводим к Path - val path = file.getType()?.let { correctPath(baseDir, it).toPath() } ?: continue - val targetPath = path.resolve(file.name.toString()).normalize() - - try { - // Создаем директории, если нужно - Files.createDirectories(targetPath.parent) - } catch (e: IOException) { - throw RuntimeException("Не удалось создать директорию: ${targetPath.parent}", e) - } - - val fileStorage = fileStorageLocator.getDefault() - fileStorage.openStream(content).use { inputStream -> - Files.copy( - inputStream, - targetPath, - StandardCopyOption.REPLACE_EXISTING - ) - } - file.getType()?.let { fileType -> - val unpackPath = when (fileType) { - REPORT -> "$baseDir${File.separator}DataProcessorsExt${File.separator}erf" - DATAPROCESSOR -> "$baseDir${File.separator}DataProcessorsExt${File.separator}epf" - else -> null - } - unpackPath?.let { - filesToUnpack.add(targetPath.toString() to unpackPath) - } - } - - } - if (filesToUnpack.isNotEmpty()) { - unpackFiles(filesToUnpack, platform, executor, baseDir) - } - } - - private fun unpackFiles(files: List>, platform: Platform, executor : ShellExecutor, baseDir: String){ - - if (files.isEmpty()){ - return - } - - for ((sourcePath, unpackPath) in files) { - ones.uploadExtFiles(File(sourcePath), unpackPath, platform.pathInstalled.toString(), platform.version.toString()) - } - - val bFiles = findBinaryFilesFromGitStatus(baseDir, executor) - if (bFiles.isEmpty()) { - return - } - bFiles.forEach { binFile -> - ones.unpackExtFiles(binFile, binFile.parent, -// platform.pathInstalled.toString(), -// platform.version.toString() - ) - } - } - - private fun findBinaryFilesFromGitStatus(repoDir: String, executor: ShellExecutor): List { - // Получаем список изменённых файлов - val gitOutput = executor.executeCommand(listOf("git", "-C", repoDir, "status", "--porcelain")).trim() - if (gitOutput.isBlank()) return emptyList() - - // Выделяем директории из вывода git status - val changedDirs = gitOutput - .lines() - .mapNotNull { line -> - val filePath = line.substringAfter(" ").takeIf { it.isNotBlank() } - filePath?.let { File(repoDir, it) } - } - .filterNotNull() - .distinct() - - // Ищем .bin файлы в изменённых директориях - val tDir = changedDirs.flatMap { dir -> - dir.walk() - .filter { file -> - file.isFile && file.name.endsWith("Form.bin", ignoreCase = false) - } - .toList() - } - return tDir - } - - private fun correctPath(baseDir: String, type: TypesFiles):File{ - return when (type) { - REPORT -> File(baseDir, "DataProcessorsExt${File.separator}Отчет${File.separator}") - DATAPROCESSOR -> File(baseDir, "DataProcessorsExt${File.separator}Обработка${File.separator}") - SCHEDULEDJOBS -> File(baseDir, "CodeExt") - EXTERNAL_CODE -> File(baseDir, "CodeExt") - EXCHANGE_RULES -> File(baseDir, "EXCHANGE_RULES") - } - } - - private fun setStatusCommit(commitId: UUID, status: StatusSheduler){ - val commit = dataManager.load(Commit::class.java) - .id(commitId) - .one() - commit.setStatus(status) - dataManager.save(commit) - } - - private fun branchExists(repoPath: String, branchName: String): Boolean { - val executor = ShellExecutor(workingDir = File(repoPath)) - val branches = executor.executeCommand(listOf("git", "branch", "--list", branchName)) - - return branches.isNotBlank() - } - - private fun firstNewDataCommit(): Commit? { - val entity = dataManager.load(Commit::class.java) - .query("select cmt from Commit_ cmt where cmt.status = :status1 order by cmt.id asc") - .parameter("status1", StatusSheduler.NEW) - .optional() - - if (entity.isEmpty) { - return null - } else { - val commit = entity.get() - commit.author = entity.get().author - commit.files = entity.get().files - commit.project = entity.get().project - - return commit - } - - } - - private fun escapeShellArgument(arg: String): String { - if (arg.isEmpty()) { - return "''" - } - - // Если строка содержит пробелы, кавычки или другие спецсимволы, заключаем её в кавычки - if (!arg.matches(Regex("^[a-zA-Z0-9_\\-+=%/:.,@]+$"))) { - return "'" + arg.replace("'", "'\"'\"'") + "'" - } - - return arg - } - - private fun sanitizeGitBranchName(input: String): String { - // Правила для имён веток Git: - // - Не могут начинаться с '-' - // - Не могут содержать: - // - пробелы - // - символы: ~, ^, :, *, ?, [, ], @, \, /, {, }, ... - // - Не могут заканчиваться на .lock - // - Не могут содержать последовательность // - - val forbiddenChars = setOf( - ' ', '~', '^', ':', '*', '?', '[', ']', '@', '\\', '/', '{', '}', - '<', '>', '|', '"', '\'', '!', '#', '$', '%', '&', '(', ')', ',', ';', '=' - ) - - return input.map { char -> - when { - char in forbiddenChars -> '_' - else -> char - } - }.joinToString("") - .removePrefix(".") // Убираем точку в начале (если есть) - .replace(Regex("[/\\\\]+"), "_") // Заменяем несколько / или \ на один _ - .replace(Regex("[._]{2,}"), "_") // Заменяем несколько точек или _ подряд на один _ - .replace(Regex("_+$"), "") // Убираем _ в конце - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/company/commitet_jm/service/file/FileServiceImpl.kt b/src/main/kotlin/com/company/commitet_jm/service/file/FileServiceImpl.kt index 6823fab..8918bd4 100644 --- a/src/main/kotlin/com/company/commitet_jm/service/file/FileServiceImpl.kt +++ b/src/main/kotlin/com/company/commitet_jm/service/file/FileServiceImpl.kt @@ -9,6 +9,7 @@ import io.jmix.core.FileStorage import io.jmix.core.FileStorageLocator import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.io.File import java.io.IOException @@ -20,13 +21,16 @@ class FileServiceImpl( private val fileStorageLocator: FileStorageLocator, private val ones: OneRunner ) : FileService { + + @Value("\${git.timeout:7}") + private var gitTimeout: Long = 7 companion object { private val log = LoggerFactory.getLogger(FileServiceImpl::class.java) } override fun saveFileCommit(baseDir: String, files: MutableList, platform: Platform) { - val executor = ShellExecutor(workingDir = File(baseDir), timeout = 7) + val executor = ShellExecutor(workingDir = File(baseDir), timeout = gitTimeout) val filesToUnpack = mutableListOf>() for (file in files) { val content = file.data ?: continue diff --git a/src/main/kotlin/com/company/commitet_jm/service/git/GitServiceImpl.kt b/src/main/kotlin/com/company/commitet_jm/service/git/GitServiceImpl.kt index 82b3d63..4d0b95f 100644 --- a/src/main/kotlin/com/company/commitet_jm/service/git/GitServiceImpl.kt +++ b/src/main/kotlin/com/company/commitet_jm/service/git/GitServiceImpl.kt @@ -9,6 +9,7 @@ import io.jmix.core.DataManager import io.jmix.core.FileStorageLocator import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.io.File import java.util.* @@ -20,13 +21,16 @@ class GitServiceImpl( private val fileService: FileService, private val oneCService: OneCService ) : GitService { + + @Value("\${git.timeout:7}") + private var gitTimeout: Long = 7 companion object { private val log = LoggerFactory.getLogger(GitServiceImpl::class.java) } override fun cloneRepo(repoUrl: String, directoryPath: String, branch: String): Pair { - val executor = ShellExecutor(timeout = 7) + val executor = ShellExecutor(timeout = gitTimeout) log.info("start clone repo $repoUrl") validateGitUrl(repoUrl) @@ -37,13 +41,18 @@ class GitServiceImpl( } return try { - executor.executeCommand(listOf( + val result = executor.executeCommandWithResult(listOf( "git", "clone", "--branch", branch, "--single-branch", + "--depth", "1", repoUrl, directoryPath )) + + if (result.exitCode != 0) { + throw RuntimeException("Failed to clone repository: ${result.error}") + } log.info("end clone repo $repoUrl") Pair(true, "") } catch (e: Exception) { @@ -101,158 +110,114 @@ class GitServiceImpl( throw IllegalArgumentException("Not a git repository") } val repoPath = commitInfo.project!!.localPath!! - val executor = ShellExecutor(workingDir = repoDir, timeout = 7) - try { - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - } catch (e: Exception) { - - if (e.message?.contains("index.lock") == true) { - log.info("Обнаружена блокировка Git. Удаление index.lock...") - - val lockFile = File("$repoDir${File.separator}.git${File.separator}index.lock") - if (lockFile.exists()) { - lockFile.delete() - log.info("Файл index.lock удалён. Повторная попытка...") - - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - } else { - throw IllegalStateException("Файл блокировки не найден, но ошибка возникла: ${e.message}") - } - } else if (e.message?.contains("would be overwritten by checkout") == true) { - - executor.executeCommand(listOf("git", "reset", "--hard")) - executor.executeCommand(listOf("git", "clean", "-fd")) - - } else { - throw e - } - } - - try { - executor.executeCommand(listOf("git", "reset", "--hard", "origin/$remoteBranch")) - } catch (e: Exception) { - log.error("Не удалось сбросить репозиторий к состоянию удаленной ветки $remoteBranch: ${e.message}") - throw RuntimeException("Не удалось сбросить репозиторий к состоянию удаленной ветки $remoteBranch", e) - } + val executor = ShellExecutor(workingDir = repoDir, timeout = gitTimeout) - try { - executor.executeCommand(listOf("git", "clean", "-fd")) - } catch (e: Exception) { - log.error("Не удалось очистить неотслеживаемые файлы: ${e.message}") - throw RuntimeException("Не удалось очистить неотслеживаемые файлы", e) - } - + // Устанавливаем статус PROCESS как можно раньше commitInfo.id?.let { setStatusCommit(it, StatusSheduler.PROCESSED) } - val remoteBranches: String - try { - remoteBranches = executor.executeCommand(listOf("git", "branch", "-a")) - } catch (e: Exception) { - log.error("Не удалось получить список веток: ${e.message}") - throw RuntimeException("Не удалось получить список веток", e) - } - - if (!remoteBranches.contains("remotes/origin/$remoteBranch")) { - throw IllegalStateException("Default branch does not exist") - } - try { - try { - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - } catch (e: Exception) { - log.error("Не удалось переключиться на ветку $remoteBranch: ${e.message}") - throw RuntimeException("Не удалось переключиться на ветку $remoteBranch", e) + // Сбрасываем репозиторий к состоянию удаленной ветки + val fetchResult = executor.executeCommandWithResult(listOf("git", "fetch", "origin", remoteBranch)) + if (fetchResult.exitCode != 0) { + log.warn("Не удалось выполнить fetch: ${fetchResult.error}") } - try { - executor.executeCommand(listOf("git", "fetch", "origin", remoteBranch)) - } catch (e: Exception) { - log.error("Не удалось получить обновления для ветки $remoteBranch: ${e.message}") - throw RuntimeException("Не удалось получить обновления для ветки $remoteBranch", e) + val resetResult = executor.executeCommandWithResult(listOf("git", "reset", "--hard", "origin/$remoteBranch")) + if (resetResult.exitCode != 0) { + log.warn("Не удалось выполнить reset: ${resetResult.error}") } - try { - executor.executeCommand(listOf("git", "checkout", remoteBranch)) - } catch (e: Exception) { - log.error("Не удалось переключиться на ветку $remoteBranch после получения обновлений: ${e.message}") - throw RuntimeException("Не удалось переключиться на ветку $remoteBranch после получения обновлений", e) + val cleanResult = executor.executeCommandWithResult(listOf("git", "clean", "-fd")) + if (cleanResult.exitCode != 0) { + log.warn("Не удалось выполнить clean: ${cleanResult.error}") } + } catch (e: Exception) { + log.error("Не удалось подготовить репозиторий: ${e.message}") + throw RuntimeException("Не удалось подготовить репозиторий", e) + } + // Работаем с веткой + try { // Проверяем существование ветки локально и удаленно - val localExists = try { - branchExists(repoPath, newBranch) - } catch (e: Exception) { - log.error("Ошибка при проверке существования локальной ветки $newBranch: ${e.message}") - throw RuntimeException("Ошибка при проверке существования локальной ветки $newBranch", e) - } - - val remoteExists = try { - remoteBranchExists(repoPath, newBranch) - } catch (e: Exception) { - log.error("Ошибка при проверке существования удаленной ветки $newBranch: ${e.message}") - throw RuntimeException("Ошибка при проверке существования удаленной ветки $newBranch", e) - } + val localExists = branchExists(repoPath, newBranch) + val remoteExists = remoteBranchExists(repoPath, newBranch) // Если ветка существует локально или удаленно, просто переходим на нее - if (localExists || remoteExists) { - log.info("Ветка $newBranch уже существует ${if (localExists && remoteExists) "(локально и удаленно)" else if (localExists) "(локально)" else "(удаленно)"}") - - // Переходим на существующую ветку - when { - localExists && remoteExists -> { - log.info("Переключаемся на локальную ветку $newBranch, которая также существует удаленно") - executor.executeCommand(listOf("git", "checkout", newBranch)) + when { + localExists && remoteExists -> { + log.info("Переключаемся на локальную ветку $newBranch, которая также существует удаленно") + val checkoutResult = executor.executeCommandWithResult(listOf("git", "checkout", newBranch)) + if (checkoutResult.exitCode != 0) { + log.warn("Не удалось переключиться на ветку $newBranch: ${checkoutResult.error}") + } + + // Проверяем наличие конфликтов перед синхронизацией + val hasConflicts = checkBranchDifference(repoPath, newBranch, "origin/$newBranch") + if (!hasConflicts) { // Синхронизируем с удаленной веткой - try { - executor.executeCommand(listOf("git", "pull", "origin", newBranch)) - } catch (e: Exception) { - log.error("Не удалось синхронизировать локальную ветку $newBranch с удаленной: ${e.message}") + val pullResult = executor.executeCommandWithResult(listOf("git", "pull", "origin", newBranch)) + if (pullResult.exitCode != 0) { + log.error("Не удалось синхронизировать локальную ветку $newBranch с удаленной: ${pullResult.error}") // Продолжаем выполнение даже при ошибке синхронизации } + } else { + log.warn("Обнаружены конфликты при слиянии веток. Пропускаем автоматическую синхронизацию.") } - localExists -> { - log.info("Переключаемся на локальную ветку $newBranch") - try { - executor.executeCommand(listOf("git", "checkout", newBranch)) - } catch (e: Exception) { - log.error("Не удалось переключиться на локальную ветку $newBranch: ${e.message}") - throw RuntimeException("Не удалось переключиться на локальную ветку $newBranch", e) - } + } + localExists -> { + log.info("Переключаемся на локальную ветку $newBranch") + val checkoutResult = executor.executeCommandWithResult(listOf("git", "checkout", newBranch, "--force")) + if (checkoutResult.exitCode != 0) { + log.warn("Не удалось переключиться на ветку $newBranch: ${checkoutResult.error}") } - remoteExists -> { - log.info("Создаем локальную ветку $newBranch из удаленной") - try { - executor.executeCommand(listOf("git", "checkout", "-b", newBranch, "origin/$newBranch")) - } catch (e: Exception) { - log.error("Не удалось создать локальную ветку $newBranch из удаленной: ${e.message}") - throw RuntimeException("Не удалось создать локальную ветку $newBranch из удаленной", e) - } + } + remoteExists -> { + log.info("Создаем локальную ветку $newBranch из удаленной") + val checkoutResult = executor.executeCommandWithResult(listOf("git", "checkout", "-b", newBranch, "origin/$newBranch", "--force")) + if (checkoutResult.exitCode != 0) { + log.warn("Не удалось создать локальную ветку $newBranch из удаленной: ${checkoutResult.error}") } } - } else { - // Создаем новую ветку - log.info("Создаем новую ветку $newBranch") - try { - executor.executeCommand(listOf("git", "checkout", "-b", newBranch)) - } catch (e: Exception) { - log.error("Не удалось создать ветку $newBranch: ${e.message}") - throw RuntimeException("Не удалось создать ветку $newBranch", e) + else -> { + // Создаем новую ветку + log.info("Создаем новую ветку $newBranch") + val checkoutResult = executor.executeCommandWithResult(listOf("git", "checkout", "-b", newBranch,"--force")) + if (checkoutResult.exitCode != 0) { + log.warn("Не удалось создать новую ветку $newBranch: ${checkoutResult.error}") + } } } } catch (e: Exception) { - log.error("beforeCmdCommit ${e.message}") - throw RuntimeException("Error cmd git ${e.message}") + log.error("Ошибка при работе с ветками: ${e.message}") + throw RuntimeException("Ошибка при работе с ветками", e) } } private fun afterCmdCommit(commitInfo: Commit, repoDir: File, newBranch: String) { - val executor = ShellExecutor(workingDir = repoDir, timeout = 7) + val executor = ShellExecutor(workingDir = repoDir, timeout = gitTimeout) + // Проверяем, есть ли изменения в репозитории перед добавлением файлов в индекс try { - executor.executeCommand(listOf("git", "add", ".")) + val statusOutput = executor.executeCommandWithResult(listOf("git", "status", "--porcelain")) + if (statusOutput.exitCode != 0) { + log.warn("Не удалось получить статус репозитория: ${statusOutput.error}") + } else if (statusOutput.output.isBlank()) { + log.info("Нет изменений для коммита в ветке $newBranch") + // Обновляем статус коммита как завершенного, даже если нет изменений + commitInfo.urlBranch = "${commitInfo.project!!.urlRepo}/tree/$newBranch" + commitInfo.setStatus(StatusSheduler.COMPLETE) + dataManager.save(commitInfo) + return + } } catch (e: Exception) { - log.error("Не удалось добавить файлы в индекс: ${e.message}") - throw RuntimeException("Не удалось добавить файлы в индекс", e) + log.error("Ошибка при проверке статуса репозитория: ${e.message}") + // Продолжаем выполнение, даже если не удалось проверить статус + } + + val addResult = executor.executeCommandWithResult(listOf("git", "add", ".")) + if (addResult.exitCode != 0) { + log.error("Не удалось добавить файлы в индекс: ${addResult.error}") + throw RuntimeException("Не удалось добавить файлы в индекс: ${addResult.error}") } // 4. Создаем коммит от указанного пользователя @@ -262,7 +227,7 @@ class GitServiceImpl( tempFile.writeText(commitMessage, Charsets.UTF_8) try { - executor.executeCommand( + val commitResult = executor.executeCommandWithResult( listOf( "git", "-c", "user.name=${commitInfo.author!!.gitLogin}", @@ -271,6 +236,11 @@ class GitServiceImpl( "-F", tempFile.absolutePath ) ) + + if (commitResult.exitCode != 0) { + log.error("Не удалось создать коммит: ${commitResult.error}") + throw RuntimeException("Не удалось создать коммит: ${commitResult.error}") + } } catch (e: Exception) { log.error("Не удалось создать коммит: ${e.message}") throw RuntimeException("Не удалось создать коммит", e) @@ -284,19 +254,19 @@ class GitServiceImpl( } log.info("git push start") - try { - executor.executeCommand(listOf("git", "push", "--force", "-u", "origin", newBranch)) - } catch (e: Exception) { - log.error("Не удалось отправить изменения в удаленный репозиторий: ${e.message}") - throw RuntimeException("Не удалось отправить изменения в удаленный репозиторий", e) + val pushResult = executor.executeCommandWithResult(listOf("git", "push", "--force", "-u", "origin", newBranch)) + if (pushResult.exitCode != 0) { + log.error("Не удалось отправить изменения в удаленный репозиторий: ${pushResult.error}") + throw RuntimeException("Не удалось отправить изменения в удаленный репозиторий: ${pushResult.error}") } log.info("git push end") - try { - commitInfo.hashCommit = executor.executeCommand(listOf("git", "rev-parse", "HEAD")) - } catch (e: Exception) { - log.error("Не удалось получить хэш коммита: ${e.message}") - throw RuntimeException("Не удалось получить хэш коммита", e) + val hashResult = executor.executeCommandWithResult(listOf("git", "rev-parse", "HEAD")) + if (hashResult.exitCode != 0) { + log.error("Не удалось получить хэш коммита: ${hashResult.error}") + throw RuntimeException("Не удалось получить хэш коммита: ${hashResult.error}") + } else { + commitInfo.hashCommit = hashResult.output.trim() } log.info("Successfully committed and pushed changes to branch $newBranch") @@ -321,10 +291,15 @@ class GitServiceImpl( } private fun branchExists(repoPath: String, branchName: String): Boolean { - val executor = ShellExecutor(workingDir = File(repoPath)) + val executor = ShellExecutor(workingDir = File(repoPath), timeout = gitTimeout) return try { - val branches = executor.executeCommand(listOf("git", "branch", "--list", branchName)) - branches.isNotBlank() + val result = executor.executeCommandWithResult(listOf("git", "branch", "--list", branchName)) + if (result.exitCode != 0) { + log.error("Ошибка при проверке существования локальной ветки $branchName: ${result.error}") + false + } else { + result.output.isNotBlank() + } } catch (e: Exception) { log.error("Ошибка при проверке существования локальной ветки $branchName: ${e.message}") false @@ -332,10 +307,15 @@ class GitServiceImpl( } private fun remoteBranchExists(repoPath: String, branchName: String): Boolean { - val executor = ShellExecutor(workingDir = File(repoPath)) + val executor = ShellExecutor(workingDir = File(repoPath), timeout = gitTimeout) return try { - val remoteBranches = executor.executeCommand(listOf("git", "branch", "-r")) - remoteBranches.lines().any { it.trim().startsWith("origin/$branchName") } + val result = executor.executeCommandWithResult(listOf("git", "branch", "-r")) + if (result.exitCode != 0) { + log.error("Ошибка при проверке существования удаленной ветки $branchName: ${result.error}") + false + } else { + result.output.lines().any { it.trim().startsWith("origin/$branchName") } + } } catch (e: Exception) { log.error("Ошибка при проверке существования удаленной ветки $branchName: ${e.message}") false @@ -343,26 +323,43 @@ class GitServiceImpl( } private fun checkBranchDifference(repoPath: String, branch1: String, branch2: String): Boolean { - val executor = ShellExecutor(workingDir = File(repoPath)) + val executor = ShellExecutor(workingDir = File(repoPath), timeout = gitTimeout) try { // Сохраняем текущую ветку, чтобы потом вернуться - val currentBranch = executor.executeCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).trim() + val currentBranchResult = executor.executeCommandWithResult(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")) + if (currentBranchResult.exitCode != 0) { + log.error("Не удалось получить текущую ветку: ${currentBranchResult.error}") + return false + } + val currentBranch = currentBranchResult.output.trim() // Проверяем, можно ли выполнить слияние без конфликтов - executor.executeCommand(listOf("git", "checkout", branch1)) + val checkoutResult = executor.executeCommandWithResult(listOf("git", "checkout", branch1)) + if (checkoutResult.exitCode != 0) { + log.error("Не удалось переключиться на ветку $branch1: ${checkoutResult.error}") + return false + } // Выполняем слияние в режиме "только проверка" (без коммита) - val mergeResult = executor.executeCommand(listOf("git", "merge", "--no-commit", "--no-ff", branch2)) + val mergeResult = executor.executeCommandWithResult(listOf("git", "merge", "--no-commit", "--no-ff", branch2)) // Проверяем, есть ли конфликты - val statusResult = executor.executeCommand(listOf("git", "status", "--porcelain")) - val hasConflicts = statusResult.lines().any { it.startsWith("UU ") } + val statusResult = executor.executeCommandWithResult(listOf("git", "status", "--porcelain")) + val hasConflicts = if (statusResult.exitCode == 0) { + statusResult.output.lines().any { it.startsWith("UU ") } + } else { + log.warn("Не удалось получить статус репозитория: ${statusResult.error}") + false + } // Также проверяем вывод команды merge на наличие сообщений о конфликтах - val hasMergeConflicts = mergeResult.contains("CONFLICT") + val hasMergeConflicts = mergeResult.exitCode != 0 || mergeResult.output.contains("CONFLICT") // Возвращаемся на исходную ветку без отмены слияния - executor.executeCommand(listOf("git", "checkout", currentBranch)) + val returnCheckoutResult = executor.executeCommandWithResult(listOf("git", "checkout", currentBranch)) + if (returnCheckoutResult.exitCode != 0) { + log.error("Не удалось вернуться на исходную ветку $currentBranch: ${returnCheckoutResult.error}") + } if (hasConflicts || hasMergeConflicts) { log.warn("Обнаружены конфликты при слиянии веток $branch1 и $branch2") @@ -375,8 +372,16 @@ class GitServiceImpl( log.warn("Ошибка при проверке различий между ветками $branch1 и $branch2: ${e.message}") try { // Возвращаемся на исходную ветку без отмены слияния - val currentBranch = executor.executeCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).trim() - executor.executeCommand(listOf("git", "checkout", currentBranch)) + val currentBranchResult = executor.executeCommandWithResult(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")) + if (currentBranchResult.exitCode == 0) { + val currentBranch = currentBranchResult.output.trim() + val returnCheckoutResult = executor.executeCommandWithResult(listOf("git", "checkout", currentBranch)) + if (returnCheckoutResult.exitCode != 0) { + log.error("Не удалось вернуться на исходную ветку: ${returnCheckoutResult.error}") + } + } else { + log.error("Не удалось получить текущую ветку: ${currentBranchResult.error}") + } } catch (abortException: Exception) { log.error("Не удалось вернуться на исходную ветку: ${abortException.message}") } diff --git a/src/main/kotlin/com/company/commitet_jm/sheduledJob/Committer.kt b/src/main/kotlin/com/company/commitet_jm/sheduledJob/Committer.kt index 4bfae42..82c805e 100644 --- a/src/main/kotlin/com/company/commitet_jm/sheduledJob/Committer.kt +++ b/src/main/kotlin/com/company/commitet_jm/sheduledJob/Committer.kt @@ -1,6 +1,5 @@ package com.company.commitet_jm.sheduledJob -import com.company.commitet_jm.service.GitWorker import com.company.commitet_jm.service.git.GitService import com.company.commitet_jm.service.ones.OneRunner import io.jmix.core.DataManager diff --git a/src/main/kotlin/com/company/commitet_jm/view/commit/CommitDetailView.kt b/src/main/kotlin/com/company/commitet_jm/view/commit/CommitDetailView.kt index 1548bfe..8280b97 100644 --- a/src/main/kotlin/com/company/commitet_jm/view/commit/CommitDetailView.kt +++ b/src/main/kotlin/com/company/commitet_jm/view/commit/CommitDetailView.kt @@ -1,7 +1,6 @@ package com.company.commitet_jm.view.commit import com.company.commitet_jm.entity.* -import com.company.commitet_jm.service.GitWorker import com.company.commitet_jm.service.git.GitService import com.company.commitet_jm.service.ones.OneRunner import com.company.commitet_jm.view.main.MainView @@ -147,12 +146,6 @@ class CommitDetailView : StandardDetailView() { @Subscribe(id = "uploadFilesButton", subject = "clickListener") private fun onUploadFilesButtonCommitClick(event: ClickEvent) { -// val gitWorker = GitWorker( -// dataManager = dataManager, -// fileStorageLocator = fileStorageLocator, -// ones = oneRunner, -// ) -// gitWorker.createCommit() gitService.createCommit() } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a51541c..a9fbb6b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,3 +27,7 @@ jmix: - jpeg - pdf +# Git configuration +git: + timeout: 7 +