From 938925e76cb6bfb5babd418a5529583b17d9bc11 Mon Sep 17 00:00:00 2001 From: shizhendong Date: Wed, 16 Oct 2024 16:13:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ASW-88=20=E6=9C=AC=E5=9C=B0=20GIT=20?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E7=AE=A1=E7=90=86=20application=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 使用 JGIT 完成对 application 文件的操作 --- .../net/geedge/asw/common/util/RCode.java | 4 + .../module/app/controller/GitController.java | 84 +++ .../asw/module/app/service/IGitService.java | 14 + .../app/service/impl/GitServiceImpl.java | 566 ++++++++++++++++-- .../resources/db/migration/R__AZ_sys_i18n.sql | 4 + 5 files changed, 626 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/geedge/asw/common/util/RCode.java b/src/main/java/net/geedge/asw/common/util/RCode.java index a93bcac..f93779c 100644 --- a/src/main/java/net/geedge/asw/common/util/RCode.java +++ b/src/main/java/net/geedge/asw/common/util/RCode.java @@ -57,6 +57,10 @@ public enum RCode { PACKAGE_DESCRIPTION_CANNOT_EMPTY(202002, "package description cannot be empty"), + // GIT + GIT_COMMIT_CONFLICT_ERROR(203001, "Commit failed; fix conflicts and then commit the result"), + + // Runner RUNNER_ID_CANNOT_EMPTY(301001, "runner id cannot be empty"), diff --git a/src/main/java/net/geedge/asw/module/app/controller/GitController.java b/src/main/java/net/geedge/asw/module/app/controller/GitController.java index 42a22ba..bbe9038 100644 --- a/src/main/java/net/geedge/asw/module/app/controller/GitController.java +++ b/src/main/java/net/geedge/asw/module/app/controller/GitController.java @@ -49,4 +49,88 @@ public class GitController { return R.ok(); } + @GetMapping("/{workspaceId}/branch/{branchName}/application") + public R listApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @RequestParam(value = "q", required = false) String q) { + List> records = gitService.listApplication(workspaceId, branchName, q); + return R.ok().putData("records", records); + } + + @GetMapping("/{workspaceId}/branch/{branchName}/application/{applicationName}") + public R infoApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("applicationName") String applicationName) { + Map record = gitService.infoApplication(workspaceId, branchName, applicationName); + return R.ok().putData("record", record); + } + + @PostMapping("/{workspaceId}/branch/{branchName}/application") + public synchronized R newApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @RequestBody Map body) { + String applicationName = T.MapUtil.getStr(body, "name"); + T.VerifyUtil.is(applicationName).notEmpty(RCode.PARAM_CANNOT_EMPTY); + + gitService.newApplication(workspaceId, branchName, applicationName); + return R.ok(); + } + + + @PostMapping("/{workspaceId}/branch/{branchName}/application/commit") + public synchronized R updateApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @RequestBody Map body) { + String lastCommitId = T.MapUtil.getStr(body, "lastCommitId"); + String message = T.MapUtil.getStr(body, "message"); + + T.VerifyUtil.is(lastCommitId).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(message).notEmpty(RCode.PARAM_CANNOT_EMPTY); + + List> files = T.MapUtil.get(body, "files", List.class, T.ListUtil.list(true)); + if (T.CollUtil.isEmpty(files)) { + return R.ok(); + } + + for (Map file : files) { + String action = T.MapUtil.getStr(file, "action"); + String path = T.MapUtil.getStr(file, "path"); + String content = T.MapUtil.getStr(file, "content"); + if (T.StrUtil.hasEmpty(action, path, content)) { + return R.error(RCode.PARAM_CANNOT_EMPTY); + } + } + gitService.updateApplication(workspaceId, branchName, lastCommitId, message, files); + return R.ok(); + } + + @DeleteMapping("/{workspaceId}/branch/{branchName}/application/{applicationName}") + public synchronized R deleteApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("applicationName") String applicationName) { + gitService.deleteApplication(workspaceId, branchName, applicationName); + return R.ok(); + } + + + @GetMapping("/{workspaceId}/branch/{branchName}/application/{applicationName}/commit") + public R listApplicationCommit(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("applicationName") String applicationName, + @RequestParam(required = false) String file) { + List> records = gitService.listApplicationCommit(workspaceId, branchName, applicationName, file); + return R.ok().putData("records", records); + } + + + @GetMapping("/{workspaceId}/branch/{branchName}/application/{applicationName}/commit/{commitId}/content") + public R infoApplicationFileContent(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("applicationName") String applicationName, + @PathVariable("commitId") String commitId, + @RequestParam(required = true) String file) { + Map record = gitService.infoApplicationFileContent(workspaceId, branchName, applicationName, commitId, file); + return R.ok().putData("record", record); + } + } \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/app/service/IGitService.java b/src/main/java/net/geedge/asw/module/app/service/IGitService.java index 1ec819c..3c469df 100644 --- a/src/main/java/net/geedge/asw/module/app/service/IGitService.java +++ b/src/main/java/net/geedge/asw/module/app/service/IGitService.java @@ -13,4 +13,18 @@ public interface IGitService { void deleteBranch(String workspaceId, String branchName); + List> listApplication(String workspaceId, String branch, String q); + + Map infoApplication(String workspaceId, String branch, String applicationName); + + void newApplication(String workspaceId, String branch, String applicationName); + + void deleteApplication(String workspaceId, String branch, String applicationName); + + void updateApplication(String workspaceId, String branch, String lastCommitId, String message, List> updateContent); + + List> listApplicationCommit(String workspaceId, String branch, String applicationName, String file); + + Map infoApplicationFileContent(String workspaceId, String branch, String applicationName, String commitId, String file); + } diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/GitServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/GitServiceImpl.java index 6110b96..81ca724 100644 --- a/src/main/java/net/geedge/asw/module/app/service/impl/GitServiceImpl.java +++ b/src/main/java/net/geedge/asw/module/app/service/impl/GitServiceImpl.java @@ -1,30 +1,37 @@ package net.geedge.asw.module.app.service.impl; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.log.Log; import net.geedge.asw.common.util.ASWException; import net.geedge.asw.common.util.RCode; import net.geedge.asw.common.util.T; import net.geedge.asw.module.app.service.IGitService; +import net.geedge.asw.module.sys.entity.SysUserEntity; +import net.geedge.asw.module.sys.service.ISysUserService; import net.geedge.asw.module.workspace.entity.WorkspaceEntity; import net.geedge.asw.module.workspace.service.IWorkspaceService; +import org.apache.commons.io.FilenameUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.*; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; 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; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service @@ -32,14 +39,32 @@ public class GitServiceImpl implements IGitService { private final static Log log = Log.get(); + @Value("${file.extensions.text:txt,csv,md,html,xml,json,log,bat,py,sh,ini,conf,yaml,yml,properties,toml,java,c,cpp,js,php,ts,go,rb,rtf,tex,rss,xhtml,sql}") + private String textExtensions; + /** * 本地分支引用前缀 */ public static final String LOCAL_BRANCH_PREFIX = "refs/heads/"; + @Autowired + private ISysUserService userService; + @Autowired private IWorkspaceService workspaceService; + /** + * is binary file + * + * @param filename + * @return + */ + private boolean isBinary(String filename) { + String extension = FilenameUtils.getExtension(filename); + List split = T.StrUtil.split(this.textExtensions, ","); + return !split.contains(extension.toLowerCase()); + } + /** * get repository path * path= {webRootPath}/workspeace/{workspace.name} @@ -53,29 +78,34 @@ public class GitServiceImpl implements IGitService { /** * get git instance */ - private Git getGitInstance(File repoDir) { + public Git getGitInstance(File repoDir) { try { - if (T.FileUtil.exist(repoDir) && T.FileUtil.file(repoDir, ".git").exists()) { - log.info("[getGitInstance] [open exist repository] [path: {}]", repoDir); + // 目录不存在,初始化裸仓库 + if (!repoDir.exists()) { + log.info("[getGitInstance] [dir not exist] [init new repository] [path: {}]", repoDir); + return Git.init() + .setBare(true) + .setDirectory(repoDir) + .call(); + } - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - builder.setGitDir(T.FileUtil.file(repoDir, ".git")); - builder.readEnvironment(); - builder.findGitDir(); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + Repository repository = builder.setGitDir(repoDir) + .readEnvironment() + .findGitDir() + .build(); - return new Git(builder.build()); + // 是否为 Git 仓库 + if (repository.getObjectDatabase().exists()) { + return new Git(repository); } else { log.info("[getGitInstance] [init new repository] [path: {}]", repoDir); - // init - Git git = Git.init().setDirectory(repoDir).call(); - // config - StoredConfig config = git.getRepository().getConfig(); - config.setString("user", null, "name", "asw"); - config.setString("user", null, "email", "asw@geedgenetworks.com"); - config.save(); - return git; + return Git.init() + .setBare(true) + .setDirectory(repoDir) + .call(); } - } catch (IOException | GitAPIException | IllegalStateException e) { + } catch (GitAPIException | IOException e) { log.error(e, "[getGitInstance] [error] [path: {}]", repoDir); throw new RuntimeException(e); } @@ -108,28 +138,8 @@ public class GitServiceImpl implements IGitService { .build(); RevCommit commit = revCommits.parseCommit(ref.getObjectId()); - List parentIds = Arrays.stream(commit.getParents()).map(RevCommit::getName).collect(Collectors.toList()); + m.put("commit", this.buildAswCommitInfo(commit)); - Map m1 = new LinkedHashMap<>(); - m1.put("id", commit.getName()); - m1.put("shortId", T.StrUtil.subPre(commit.getName(), 8)); - m1.put("createdAt", commit.getCommitTime()); - - m1.put("title", commit.getShortMessage()); - m1.put("message", commit.getFullMessage()); - m1.put("parentIds", parentIds); - - PersonIdent authorIdent = commit.getAuthorIdent(); - m1.put("authorName", authorIdent.getName()); - m1.put("authorEmail", authorIdent.getEmailAddress()); - m1.put("authoredDate", authorIdent.getWhen().getTime()); - - PersonIdent committerIdent = commit.getCommitterIdent(); - m1.put("committerName", committerIdent.getName()); - m1.put("committerEmail", committerIdent.getEmailAddress()); - m1.put("committedDate", committerIdent.getWhen().getTime()); - - m.put("commit", m1); resultList.add(m); } } catch (GitAPIException | IOException e) { @@ -179,4 +189,468 @@ public class GitServiceImpl implements IGitService { } } + @Override + public List> listApplication(String workspaceId, String branch, String q) { + List> resultList = T.ListUtil.list(true); + + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + try (TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository)) { + + ObjectId branchRef = repository.resolve(branch); + treeWalk.addTree(revWalk.parseTree(branchRef)); + treeWalk.setFilter(PathFilter.create("applications/")); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + String fileName = treeWalk.getNameString(); + if (T.StrUtil.equals("basic.json", fileName)) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + String basicJsonStr = T.StrUtil.utf8Str(loader.getBytes()); + basicJsonStr = T.StrUtil.emptyToDefault(basicJsonStr, T.StrUtil.EMPTY_JSON); + Map basicJsonMap = T.JSONUtil.toBean(basicJsonStr, Map.class); + + Map m = T.MapUtil.newHashMap(true); + m.putAll(basicJsonMap); + + Iterable iterable = git.log() + .add(branchRef) + .addPath(treeWalk.getPathString().replaceAll(fileName, "")) + .call(); + Iterator iterator = iterable.iterator(); + RevCommit commit = iterator.hasNext() ? iterator.next() : null; + m.put("commit", this.buildAswCommitInfo(commit)); + + resultList.add(m); + } + } + } + } catch (IOException | GitAPIException e) { + log.error(e, "[listApplication] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch); + throw new ASWException(RCode.ERROR); + } + return resultList; + } + + @Override + public Map infoApplication(String workspaceId, String branch, String applicationName) { + Map result = T.MapUtil.builder() + .put("branch", branch) + .build(); + + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + // 获取指定版本的最新ID + ObjectId branchRef = repository.resolve(branch); + result.put("commitId", branchRef.getName()); + + List files = T.ListUtil.list(true); + try (TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository)) { + + treeWalk.addTree(revWalk.parseTree(branchRef)); + treeWalk.setFilter(PathFilter.create("applications/" + applicationName + "/")); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + Map m = T.MapUtil.builder() + .put("path", treeWalk.getPathString()) + .build(); + + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + if (this.isBinary(treeWalk.getNameString())) { + String content = Base64.getEncoder().encodeToString(loader.getBytes()); + m.put("encoding", "base64"); + m.put("content", content); + } else { + m.put("encoding", ""); + m.put("content", T.StrUtil.utf8Str(loader.getBytes())); + } + + // lastCommitId + Iterable iterable = git.log() + .add(branchRef) + .addPath(treeWalk.getPathString()) + .call(); + Iterator iterator = iterable.iterator(); + m.put("lastCommitId", iterator.hasNext() ? iterator.next().getName() : null); + files.add(m); + } + } + result.put("files", files); + } catch (IOException | GitAPIException e) { + log.error(e, "[infoApplication] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); + throw new ASWException(RCode.ERROR); + } + return result; + } + + @Override + public void newApplication(String workspaceId, String branch, String applicationName) { + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + Map filePathAndBlobIdMap = T.MapUtil.newHashMap(true); + for (String str : T.ListUtil.of("README.md", "basic.json", "signature.json", "icon.png")) { + String savePath = T.StrUtil.concat(true, "applications/", applicationName, "/", str); + + ObjectId objectId = this.insertBlobFileToDatabase(repository, T.StrUtil.EMPTY.getBytes()); + filePathAndBlobIdMap.put(savePath, objectId); + } + + try (ObjectInserter inserter = repository.getObjectDatabase().newInserter(); + TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository)) { + + // branch tree + ObjectId resolveId = repository.resolve(branch); + if (null != resolveId) { + treeWalk.addTree(revWalk.parseTree(resolveId)); + } else { + treeWalk.addTree(new CanonicalTreeParser()); + } + treeWalk.setRecursive(true); + + DirCache newTree = DirCache.newInCore(); + DirCacheBuilder newTreeBuilder = newTree.builder(); + while (treeWalk.next()) { + DirCacheEntry entry = new DirCacheEntry(treeWalk.getPathString()); + entry.setFileMode(treeWalk.getFileMode(0)); + entry.setObjectId(treeWalk.getObjectId(0)); + newTreeBuilder.add(entry); + } + + // update new tree + for (Map.Entry entry : filePathAndBlobIdMap.entrySet()) { + String filePath = entry.getKey(); + ObjectId blobId = entry.getValue(); + + // add file ref + DirCacheEntry dirCacheEntry = new DirCacheEntry(filePath); + dirCacheEntry.setFileMode(FileMode.REGULAR_FILE); + dirCacheEntry.setObjectId(blobId); + newTreeBuilder.add(dirCacheEntry); + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + + String message = String.format("feat: add %s application", applicationName); + this.createCommit(repository, branch, newTreeId, message); + } + } catch (IOException e) { + log.error(e, "[newApplication] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); + throw new ASWException(RCode.ERROR); + } + } + + @Override + public void deleteApplication(String workspaceId, String branch, String applicationName) { + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + try (ObjectInserter inserter = repository.getObjectDatabase().newInserter(); + TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository)) { + + ObjectId branchRef = repository.resolve(branch); + treeWalk.addTree(revWalk.parseTree(branchRef)); + treeWalk.setRecursive(true); + + DirCache newTree = DirCache.newInCore(); + DirCacheBuilder newTreeBuilder = newTree.builder(); + + String appFilePrefixStr = T.StrUtil.concat(true, "applications/", applicationName, "/"); + while (treeWalk.next()) { + String pathString = treeWalk.getPathString(); + if (!pathString.startsWith(appFilePrefixStr)) { + DirCacheEntry entry = new DirCacheEntry(pathString); + entry.setFileMode(treeWalk.getFileMode(0)); + entry.setObjectId(treeWalk.getObjectId(0)); + newTreeBuilder.add(entry); + } + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + + String message = String.format("refactor: remove %s application", applicationName); + this.createCommit(repository, branch, newTreeId, message); + } + } catch (IOException e) { + log.error(e, "[deleteApplication] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); + throw new ASWException(RCode.ERROR); + } + } + + @Override + public void updateApplication(String workspaceId, String branch, String lastCommitId, String message, List> updateContent) { + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + // 分支最新 commitId + ObjectId branchRef = repository.resolve(branch); + // 如果不相等,检查 param.lastCommitId --- branch.HEAD 期间是否更新了本次修改文件 + if (!T.StrUtil.equals(lastCommitId, branchRef.getName())) { + List updateFilePath = updateContent.stream() + .filter(map -> { + String action = T.MapUtil.getStr(map, "action"); + return T.StrUtil.equalsAny(action, "create", "update"); + }) + .map(map -> T.MapUtil.getStr(map, "path")) + .collect(Collectors.toList()); + + ObjectId currentCommitId = repository.resolve(lastCommitId); + try (RevWalk walk = new RevWalk(repository)) { + CanonicalTreeParser c1 = new CanonicalTreeParser(); + c1.reset(repository.newObjectReader(), walk.parseCommit(currentCommitId).getTree()); + + CanonicalTreeParser c2 = new CanonicalTreeParser(); + c2.reset(repository.newObjectReader(), walk.parseCommit(branchRef).getTree()); + + List diffs = git.diff() + .setOldTree(c1) + .setNewTree(c2) + .call(); + List affectFilePathList = diffs.stream() + .filter(entry -> T.StrUtil.equalsAnyIgnoreCase(entry.getChangeType().name(), DiffEntry.ChangeType.MODIFY.name(), DiffEntry.ChangeType.ADD.name())) + .map(DiffEntry::getNewPath) + .collect(Collectors.toList()); + List intersection = T.CollUtil.intersection(updateFilePath, affectFilePathList).stream().toList(); + if (T.CollUtil.isEmpty(intersection)) { + // 在 param.lastCommitId 不是当前分支最新提交时,本次提交文件没有冲突,更新 commit=branch.HEAD + lastCommitId = branchRef.getName(); + } else { + throw new ASWException(RCode.GIT_COMMIT_CONFLICT_ERROR); + } + } + } + try (ObjectInserter inserter = repository.getObjectDatabase().newInserter(); + TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository)) { + + // branch tree + treeWalk.addTree(revWalk.parseTree(branchRef)); + treeWalk.setRecursive(true); + + DirCache newTree = DirCache.newInCore(); + DirCacheBuilder newTreeBuilder = newTree.builder(); + + List updateFilePath = updateContent.stream().map(map -> T.MapUtil.getStr(map, "path")).collect(Collectors.toList()); + while (treeWalk.next()) { + String pathString = treeWalk.getPathString(); + // 先删 + if (!updateFilePath.contains(pathString)) { + DirCacheEntry entry = new DirCacheEntry(pathString); + entry.setFileMode(treeWalk.getFileMode(0)); + entry.setObjectId(treeWalk.getObjectId(0)); + newTreeBuilder.add(entry); + } + } + // 后增 + for (Map map : updateContent) { + String action = T.MapUtil.getStr(map, "action"); + if (T.StrUtil.equalsAnyIgnoreCase(action, "create", "update")) { + String path = T.MapUtil.getStr(map, "path"); + DirCacheEntry dirCacheEntry = new DirCacheEntry(path); + dirCacheEntry.setFileMode(FileMode.REGULAR_FILE); + + String content = T.MapUtil.getStr(map, "content"); + String encoding = T.MapUtil.getStr(map, "encoding"); + if ("base64".equals(encoding)) { + // binary + byte[] data = Base64.getDecoder().decode(content); + ObjectId objectId = this.insertBlobFileToDatabase(repository, data); + dirCacheEntry.setObjectId(objectId); + } else { + // text + ObjectId objectId = this.insertBlobFileToDatabase(repository, content.getBytes()); + dirCacheEntry.setObjectId(objectId); + } + newTreeBuilder.add(dirCacheEntry); + } + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + this.createCommit(repository, branch, newTreeId, message); + } + } catch (IOException | GitAPIException e) { + log.error(e, "[updateApplication] [error] [workspaceId: {}] [branch: {}] [lastCommitId: {}]", workspaceId, branch, lastCommitId); + throw new ASWException(RCode.ERROR); + } + } + + @Override + public List> listApplicationCommit(String workspaceId, String branch, String applicationName, String file) { + List> resultList = T.ListUtil.list(true); + + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = this.getGitInstance(repoDir)) { + String filterPath = T.StrUtil.concat(true, "applications/", applicationName); + if (T.StrUtil.isNotEmpty(file)) { + filterPath = T.StrUtil.concat(true, filterPath, "/", file); + } + + ObjectId branchRef = git.getRepository().resolve(branch); + Iterable iterable = git.log() + .add(branchRef) + .addPath(filterPath) + .call(); + + for (RevCommit commit : iterable) { + resultList.add(this.buildAswCommitInfo(commit)); + } + } catch (IOException | GitAPIException e) { + log.error(e, "[listApplicationCommit] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); + throw new ASWException(RCode.ERROR); + } + return resultList; + } + + @Override + public Map infoApplicationFileContent(String workspaceId, String branch, String applicationName, String commitId1111, String file) { + // applications/qq/basic.json + String path = T.StrUtil.concat(true, "applications/", applicationName, "/", file); + + Map result = T.MapUtil.builder() + .put("path", path) + .build(); + + File repoDir = this.getRepoDirPath(workspaceId); + try (Git git = getGitInstance(repoDir)) { + Repository repository = git.getRepository(); + + try (TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository); + ) { + RevCommit revCommit = revWalk.parseCommit(ObjectId.fromString(commitId1111)); + ObjectId treeId = revCommit.getTree().getId(); + + treeWalk.addTree(treeId); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + if (T.StrUtil.equals(path, treeWalk.getPathString())) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + if (this.isBinary(treeWalk.getNameString())) { + String content = Base64.getEncoder().encodeToString(loader.getBytes()); + result.put("encoding", "base64"); + result.put("content", content); + } else { + result.put("encoding", ""); + result.put("content", T.StrUtil.utf8Str(loader.getBytes())); + } + } + } + } + } catch (IOException e) { + log.error(e, "[infoApplicationFileContent] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); + throw new ASWException(RCode.ERROR); + } + return result; + } + + /** + * build asw commit info + * + * @param commit + * @return + */ + private Map buildAswCommitInfo(RevCommit commit) { + if (null == commit) { + return T.MapUtil.newHashMap(); + } + + Map m = new LinkedHashMap<>(); + m.put("id", commit.getName()); + m.put("shortId", T.StrUtil.subPre(commit.getName(), 8)); + m.put("createdAt", TimeUnit.SECONDS.toMillis(commit.getCommitTime())); + + m.put("title", commit.getShortMessage()); + m.put("message", commit.getFullMessage()); + + List parentIds = Arrays.stream(commit.getParents()).map(RevCommit::getName).collect(Collectors.toList()); + m.put("parentIds", parentIds); + + PersonIdent authorIdent = commit.getAuthorIdent(); + m.put("authorName", authorIdent.getName()); + m.put("authorEmail", authorIdent.getEmailAddress()); + m.put("authoredDate", authorIdent.getWhen().getTime()); + + PersonIdent committerIdent = commit.getCommitterIdent(); + m.put("committerName", committerIdent.getName()); + m.put("committerEmail", committerIdent.getEmailAddress()); + m.put("committedDate", committerIdent.getWhen().getTime()); + return m; + } + + /** + * 将 file object 添加到 objectDatabases,也就是 ./objects 目录下 + * + * @param repository + * @param data + * @return + * @throws IOException + */ + private ObjectId insertBlobFileToDatabase(Repository repository, byte[] data) throws IOException { + try (ObjectInserter inserter = repository.getObjectDatabase().newInserter()) { + ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, data); + inserter.flush(); + return blobId; + } + } + + /** + * commit + * + * @param repo + * @param branch + * @param treeId + * @param message + * @throws IOException + */ + public void createCommit(Repository repo, String branch, ObjectId treeId, String message) throws IOException { + try (ObjectInserter inserter = repo.getObjectDatabase().newInserter(); + RevWalk revWalk = new RevWalk(repo);) { + + CommitBuilder builder = new CommitBuilder(); + builder.setTreeId(treeId); + builder.setMessage(message); + + ObjectId branchRef = repo.resolve(branch); + if (null != branchRef) { + RevCommit parentId = revWalk.parseCommit(branchRef); + builder.setParentId(parentId); + } + + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + builder.setAuthor(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com")); + builder.setCommitter(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com")); + + // 插入新的提交对象 + ObjectId commitId = inserter.insert(builder); + inserter.flush(); + + // 更新 branch 指向新的提交 + if (null != branchRef) { + RefUpdate refUpdate = repo.updateRef(branch); + refUpdate.setNewObjectId(commitId); + refUpdate.update(); + } else { + RefUpdate refUpdate = repo.updateRef(Constants.HEAD); + refUpdate.setNewObjectId(commitId); + refUpdate.update(); + } + } + } + } \ No newline at end of file diff --git a/src/main/resources/db/migration/R__AZ_sys_i18n.sql b/src/main/resources/db/migration/R__AZ_sys_i18n.sql index 886415b..7a91761 100644 --- a/src/main/resources/db/migration/R__AZ_sys_i18n.sql +++ b/src/main/resources/db/migration/R__AZ_sys_i18n.sql @@ -139,5 +139,9 @@ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (231, '302002', 'PLAYBOOK_NAME_DUPLICATE', '剧本名称重复', 'zh', '', 'admin', 1724030366000); INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (232, '601005', 'ENVIRONMENT_ID_CANNOT_EMPTY', 'environment id cannot be empty', 'en', '', 'admin', 1724030366000); INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (233, '601005', 'ENVIRONMENT_ID_CANNOT_EMPTY', '环境 id 不能为空', 'zh', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (234, '202003', 'PACKAGE_FILE_TYPE_ERROR', 'package invalid file', 'en', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (235, '202003', 'PACKAGE_FILE_TYPE_ERROR', '无效安装包文件', 'zh', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (236, '203001', 'GIT_COMMIT_CONFLICT_ERROR', 'Commit failed; fix conflicts and then commit the result', 'en', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (237, '203001', 'GIT_COMMIT_CONFLICT_ERROR', '提交失败;解决冲突,然后提交结果', 'zh', '', 'admin', 1724030366000); SET FOREIGN_KEY_CHECKS = 1;