diff --git a/src/main/java/net/geedge/asw/module/app/controller/ApplicationController.java b/src/main/java/net/geedge/asw/module/app/controller/ApplicationController.java index c11b66e..55c488a 100644 --- a/src/main/java/net/geedge/asw/module/app/controller/ApplicationController.java +++ b/src/main/java/net/geedge/asw/module/app/controller/ApplicationController.java @@ -1,7 +1,6 @@ package net.geedge.asw.module.app.controller; import cn.hutool.json.JSONObject; -import cn.hutool.log.Log; import jakarta.servlet.http.HttpServletResponse; import net.geedge.asw.common.util.ASWException; import net.geedge.asw.common.util.R; @@ -25,14 +24,100 @@ import java.util.stream.Collectors; @RequestMapping("/api/v1/workspace") public class ApplicationController { - private static final Log log = Log.get(); - @Autowired private IWorkspaceService workspaceService; @Autowired private IApplicationService applicationService; + @GetMapping("/{workspaceId}/branch/{branchName}/application/{applicationName}") + public R infoApplication(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("applicationName") String applicationName) { + Map record = applicationService.infoApplication(workspaceId, branchName, applicationName); + return R.ok().putData("record", record); + } + + @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 = applicationService.listApplication(workspaceId, branchName, q); + return R.ok().putData("records", records); + } + + @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); + + applicationService.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"); + if (T.StrUtil.hasEmpty(action, path)) { + return R.error(RCode.PARAM_CANNOT_EMPTY); + } + if (T.StrUtil.equalsAny(action, "create", "update")) { + String content = T.MapUtil.getStr(file, "content"); + T.VerifyUtil.is(content).notEmpty(RCode.PARAM_CANNOT_EMPTY); + } + } + applicationService.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) { + applicationService.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 = applicationService.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 = applicationService.infoApplicationFileContent(workspaceId, branchName, applicationName, commitId, file); + return R.ok().putData("record", record); + } + + @PostMapping("/{workspaceId}/branch/{branchName}/application/import") public synchronized R importApplication(@PathVariable("workspaceId") String workspaceId, @PathVariable("branchName") String branchName, @@ -85,7 +170,7 @@ public class ApplicationController { @RequestParam(defaultValue = "tsg2402") String format, HttpServletResponse response) throws IOException { // validate - List appList = applicationService.queryList(workspaceId, branchName, T.StrUtil.splitToArray(names, ",")); + List appList = applicationService.listApplication(workspaceId, branchName, T.StrUtil.splitToArray(names, ",")); T.VerifyUtil.is(appList).notEmpty(RCode.APP_NOT_EXIST); // format @@ -95,4 +180,4 @@ public class ApplicationController { T.ResponseUtil.downloadFile(response, T.StrUtil.concat(true, "application_", System.currentTimeMillis() + ".json"), bytes); } -} +} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/app/controller/BranchController.java b/src/main/java/net/geedge/asw/module/app/controller/BranchController.java new file mode 100644 index 0000000..bd8fd8e --- /dev/null +++ b/src/main/java/net/geedge/asw/module/app/controller/BranchController.java @@ -0,0 +1,111 @@ +package net.geedge.asw.module.app.controller; + +import net.geedge.asw.common.util.ASWException; +import net.geedge.asw.common.util.R; +import net.geedge.asw.common.util.RCode; +import net.geedge.asw.common.util.T; +import net.geedge.asw.module.app.service.IBranchService; +import net.geedge.asw.module.app.util.JGitUtils; +import net.geedge.asw.module.workspace.service.IWorkspaceService; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/workspace") +public class BranchController { + + @Autowired + private IBranchService branchService; + + @Autowired + private IWorkspaceService workspaceService; + + @GetMapping("/{workspaceId}/branch") + public R listBranch(@PathVariable("workspaceId") String workspaceId, + @RequestParam(value = "search", required = false) String search) { + List> list = branchService.listBranch(workspaceId, search); + return R.ok().putData("records", list); + } + + @GetMapping("/{workspaceId}/branch/{branchName}") + public R infoBranch(@PathVariable("workspaceId") String workspaceId, @PathVariable("branchName") String branchName) { + Map record = branchService.infoBranch(workspaceId, branchName); + return R.ok().putData("record", record); + } + + @PostMapping("/{workspaceId}/branch") + public synchronized R newBranch(@PathVariable("workspaceId") String workspaceId, @RequestBody Map requestBody) { + String branch = T.MapUtil.getStr(requestBody, "branch", ""); + String ref = T.MapUtil.getStr(requestBody, "ref", ""); + if (T.StrUtil.hasEmpty(branch, ref)) { + throw new ASWException(RCode.PARAM_CANNOT_EMPTY); + } + + Map record = branchService.newBranch(workspaceId, branch, ref); + return R.ok().putData("record", record); + } + + @DeleteMapping("/{workspaceId}/branch/{branchName}") + public synchronized R deleteBranch(@PathVariable("workspaceId") String workspaceId, @PathVariable("branchName") String branchName) { + branchService.deleteBranch(workspaceId, branchName); + return R.ok(); + } + + @GetMapping("/{workspaceId}/branch/{branchName}/commits") + public R listBranchCommit(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @RequestParam(required = false) String path, + @RequestParam(required = false) Integer current, + @RequestParam(required = false) Integer size) { + List> records = branchService.listBranchCommit(workspaceId, branchName, path); + long total = T.CollUtil.size(records); + if (null != current && null != size) { + int fromIndex = (current - 1) * size; + int toIndex = Math.min(fromIndex + size, records.size()); + records = records.subList(fromIndex, toIndex); + } + return R.ok().putData("records", records).put("total", total); + } + + @GetMapping({ + "/{workspaceId}/branch/{branchName}/commit/{commitId}/diff", + "/{workspaceId}/branch/{branchName}/commit/{commitId}/diff/{oldCommitId}" + }) + public R branchCommitDiff(@PathVariable("workspaceId") String workspaceId, + @PathVariable("branchName") String branchName, + @PathVariable("commitId") String newCommitId, + @PathVariable(value = "oldCommitId", required = false) String oldCommitId) throws IOException { + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + if (T.StrUtil.isEmpty(oldCommitId)) { + // oldCommitId 为空默认对比 parent commitId + RevCommit parentCommit = JGitUtils.getParentCommit(repository, branchName, newCommitId); + oldCommitId = parentCommit.getName(); + } + + RevCommit oldCommit = JGitUtils.infoCommit(repository, oldCommitId); + RevCommit newCommit = JGitUtils.infoCommit(repository, newCommitId); + + List> diffList = JGitUtils.getDiffFileListInCommits(repository, newCommitId, oldCommitId); + Map record = T.MapUtil.builder() + .put("oldBranch", branchName) + .put("newBranch", branchName) + .put("oldCommitId", oldCommitId) + .put("newCommitId", newCommitId) + .put("oldCommit", JGitUtils.buildAswCommitInfo(oldCommit)) + .put("newCommit", JGitUtils.buildAswCommitInfo(newCommit)) + .put("files", diffList) + .build(); + return R.ok().putData("record", record); + } + } + +} \ No newline at end of file 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 deleted file mode 100644 index 58dfce8..0000000 --- a/src/main/java/net/geedge/asw/module/app/controller/GitController.java +++ /dev/null @@ -1,277 +0,0 @@ -package net.geedge.asw.module.app.controller; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import net.geedge.asw.common.util.ASWException; -import net.geedge.asw.common.util.R; -import net.geedge.asw.common.util.RCode; -import net.geedge.asw.common.util.T; -import net.geedge.asw.module.app.entity.ApplicationMergeEntity; -import net.geedge.asw.module.app.service.IApplicationMergeService; -import net.geedge.asw.module.app.service.IGitService; -import net.geedge.asw.module.app.service.impl.ApplicationMergeServiceImpl.MergeRequestStatus; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/v1/workspace") -public class GitController { - - @Autowired - private IGitService gitService; - - @Autowired - private IApplicationMergeService applicationMergeService; - - @GetMapping("/{workspaceId}/branch") - public R listBranch(@PathVariable("workspaceId") String workspaceId, - @RequestParam(value = "search", required = false) String search) { - List> list = gitService.listBranch(workspaceId, search); - return R.ok().putData("records", list); - } - - @GetMapping("/{workspaceId}/branch/{branchName}") - public R infoBranch(@PathVariable("workspaceId") String workspaceId, @PathVariable("branchName") String branchName) { - Map record = gitService.infoBranch(workspaceId, branchName); - return R.ok().putData("record", record); - } - - @PostMapping("/{workspaceId}/branch") - public R newBranch(@PathVariable("workspaceId") String workspaceId, @RequestBody Map requestBody) { - String branch = T.MapUtil.getStr(requestBody, "branch", ""); - String ref = T.MapUtil.getStr(requestBody, "ref", ""); - if (T.StrUtil.hasEmpty(branch, ref)) { - throw new ASWException(RCode.PARAM_CANNOT_EMPTY); - } - - Map record = gitService.newBranch(workspaceId, branch, ref); - return R.ok().putData("record", record); - } - - @DeleteMapping("/{workspaceId}/branch/{branchName}") - public R deleteBranch(@PathVariable("workspaceId") String workspaceId, @PathVariable("branchName") String branchName) { - gitService.deleteBranch(workspaceId, branchName); - return R.ok(); - } - - @GetMapping("/{workspaceId}/branch/{branchName}/commits") - public R listBranchCommit(@PathVariable("workspaceId") String workspaceId, - @PathVariable("branchName") String branchName, - @RequestParam(required = false) String path, - @RequestParam(required = false) Integer current, - @RequestParam(required = false) Integer size) { - List> records = gitService.listBranchCommit(workspaceId, branchName, path); - long total = T.CollUtil.size(records); - if (null != current && null != size) { - int fromIndex = (current - 1) * size; - int toIndex = Math.min(fromIndex + size, records.size()); - records = records.subList(fromIndex, toIndex); - } - return R.ok().putData("records", records).put("total", total); - } - - @GetMapping({ - "/{workspaceId}/branch/{branchName}/commit/{commitId}/diff", - "/{workspaceId}/branch/{branchName}/commit/{commitId}/diff/{oldCommitId}" - }) - public R branchCommitDiff(@PathVariable("workspaceId") String workspaceId, - @PathVariable("branchName") String branchName, - @PathVariable("commitId") String newCommitId, - @PathVariable(value = "oldCommitId", required = false) String oldCommitId) { - - if (T.StrUtil.isEmpty(oldCommitId)) { - // oldCommitId 为空默认对比 parent commitId - oldCommitId = gitService.getParentCommitId(workspaceId, branchName, newCommitId); - } - - Map oldCommit = gitService.infoCommit(workspaceId, oldCommitId); - Map newCommit = gitService.infoCommit(workspaceId, newCommitId); - - List> diffList = gitService.getDiffFileListInCommits(workspaceId, newCommitId, oldCommitId); - Map record = T.MapUtil.builder() - .put("oldBranch", branchName) - .put("newBranch", branchName) - .put("oldCommitId", oldCommitId) - .put("newCommitId", newCommitId) - .put("oldCommit", oldCommit) - .put("newCommit", newCommit) - .put("files", diffList) - .build(); - return R.ok().putData("record", record); - } - - @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"); - if (T.StrUtil.hasEmpty(action, path)) { - return R.error(RCode.PARAM_CANNOT_EMPTY); - } - if (T.StrUtil.equalsAny(action, "create", "update")) { - String content = T.MapUtil.getStr(file, "content"); - T.VerifyUtil.is(content).notEmpty(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); - } - - @GetMapping("/{workspaceId}/mr") - public R listMr(@PathVariable("workspaceId") String workspaceId, @RequestParam Map params) { - // workspaceId - params = T.MapUtil.defaultIfEmpty(params, new HashMap<>()); - params.put("workspaceId", workspaceId); - - Page page = applicationMergeService.queryList(params); - return R.ok(page); - } - - @GetMapping("/{workspaceId}/mr/{mrId}") - public R infoMr(@PathVariable("mrId") String mrId) { - ApplicationMergeEntity record = applicationMergeService.queryInfo(mrId); - return R.ok().putData("record", record); - } - - @GetMapping("/{workspaceId}/mr/{mrId}/commits") - public R listMrCommit(@PathVariable("mrId") String mrId) { - List> records = applicationMergeService.listMrCommit(mrId); - return R.ok().putData("records", records); - } - - @GetMapping("/{workspaceId}/mr/{mrId}/diffs") - public R mrDiff(@PathVariable("mrId") String mrId) { - Map record = applicationMergeService.mrCommitDiff(mrId); - return R.ok().putData("record", record); - } - - @GetMapping("/{workspaceId}/mr/{mrId}/conflicts") - public R getMrConflictList(@PathVariable("mrId") String mrId) { - Map record = applicationMergeService.getMrConflictList(mrId); - return R.ok().putData("record", record); - } - - @PostMapping("/{workspaceId}/mr/{mrId}/conflicts") - public R resolveMrConflicts(@PathVariable("mrId") String mrId, @RequestBody Map body) { - String commitId = T.MapUtil.getStr(body, "commitId"); - String commitMessage = T.MapUtil.getStr(body, "commitMessage"); - List> files = T.MapUtil.get(body, "files", List.class, new ArrayList()); - T.VerifyUtil.is(commitId).notEmpty(RCode.PARAM_CANNOT_EMPTY) - .and(commitMessage).notEmpty(RCode.PARAM_CANNOT_EMPTY) - .and(files).notEmpty(RCode.PARAM_CANNOT_EMPTY); - - for (Map m : files) { - String path = T.MapUtil.getStr(m, "path"); - String content = T.MapUtil.getStr(m, "content"); - T.VerifyUtil.is(path).notEmpty(RCode.PARAM_CANNOT_EMPTY) - .and(content).notEmpty(RCode.PARAM_CANNOT_EMPTY); - } - - applicationMergeService.resolveMrConflicts(mrId, body); - return R.ok(); - } - - @PostMapping("/{workspaceId}/mr") - public R newMr(@PathVariable("workspaceId") String workspaceId, @RequestBody ApplicationMergeEntity entity) { - T.VerifyUtil.is(entity).notNull() - .and(entity.getTitle()).notEmpty(RCode.PARAM_CANNOT_EMPTY) - .and(entity.getTargetBranch()).notEmpty(RCode.PARAM_CANNOT_EMPTY) - .and(entity.getTargetBranch()).notEmpty(RCode.PARAM_CANNOT_EMPTY); - - // workspaceId - entity.setWorkspaceId(workspaceId); - - ApplicationMergeEntity record = applicationMergeService.newMr(entity); - return R.ok().putData("record", record); - } - - @PutMapping("/{workspaceId}/mr/{mrId}") - public synchronized R mergeMr(@PathVariable("mrId") String mrId, @RequestBody(required = false) Map body) { - String action = T.MapUtil.getStr(body, "action"); - T.VerifyUtil.is(action).notEmpty(RCode.PARAM_CANNOT_EMPTY); - - ApplicationMergeEntity record = applicationMergeService.mergeMr(mrId, action); - if(T.BooleanUtil.and( - T.ObjectUtil.equals("merge", action), - T.ObjectUtil.notEqual(MergeRequestStatus.MERGED.toString(), record.getStatus()) - )){ - return R.error(RCode.GIT_MERGE_FAILED).putData("record", record); - } - return R.ok().putData("record", record); - } - -} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/app/controller/MergeRequestController.java b/src/main/java/net/geedge/asw/module/app/controller/MergeRequestController.java new file mode 100644 index 0000000..9d4eb5e --- /dev/null +++ b/src/main/java/net/geedge/asw/module/app/controller/MergeRequestController.java @@ -0,0 +1,109 @@ +package net.geedge.asw.module.app.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.geedge.asw.common.util.R; +import net.geedge.asw.common.util.RCode; +import net.geedge.asw.common.util.T; +import net.geedge.asw.module.app.entity.ApplicationMergeEntity; +import net.geedge.asw.module.app.service.IApplicationMergeService; +import net.geedge.asw.module.app.service.impl.ApplicationMergeServiceImpl.MergeRequestStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/workspace") +public class MergeRequestController { + + @Autowired + private IApplicationMergeService applicationMergeService; + + @GetMapping("/{workspaceId}/mr") + public R listMr(@PathVariable("workspaceId") String workspaceId, @RequestParam Map params) { + // workspaceId + params = T.MapUtil.defaultIfEmpty(params, new HashMap<>()); + params.put("workspaceId", workspaceId); + + Page page = applicationMergeService.queryList(params); + return R.ok(page); + } + + @GetMapping("/{workspaceId}/mr/{mrId}") + public R infoMr(@PathVariable("mrId") String mrId) { + ApplicationMergeEntity record = applicationMergeService.queryInfo(mrId); + return R.ok().putData("record", record); + } + + @GetMapping("/{workspaceId}/mr/{mrId}/commits") + public R listMrCommit(@PathVariable("mrId") String mrId) throws IOException { + List> records = applicationMergeService.listMrCommit(mrId); + return R.ok().putData("records", records); + } + + @GetMapping("/{workspaceId}/mr/{mrId}/diffs") + public R mrDiff(@PathVariable("mrId") String mrId) { + Map record = applicationMergeService.mrCommitDiff(mrId); + return R.ok().putData("record", record); + } + + @GetMapping("/{workspaceId}/mr/{mrId}/conflicts") + public R getMrConflictList(@PathVariable("mrId") String mrId) throws IOException { + Map record = applicationMergeService.getMrConflictList(mrId); + return R.ok().putData("record", record); + } + + @PostMapping("/{workspaceId}/mr/{mrId}/conflicts") + public R resolveMrConflicts(@PathVariable("mrId") String mrId, @RequestBody Map body) throws IOException{ + String commitId = T.MapUtil.getStr(body, "commitId"); + String commitMessage = T.MapUtil.getStr(body, "commitMessage"); + List> files = T.MapUtil.get(body, "files", List.class, new ArrayList()); + T.VerifyUtil.is(commitId).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(commitMessage).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(files).notEmpty(RCode.PARAM_CANNOT_EMPTY); + + for (Map m : files) { + String path = T.MapUtil.getStr(m, "path"); + String content = T.MapUtil.getStr(m, "content"); + T.VerifyUtil.is(path).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(content).notEmpty(RCode.PARAM_CANNOT_EMPTY); + } + + applicationMergeService.resolveMrConflicts(mrId, body); + return R.ok(); + } + + @PostMapping("/{workspaceId}/mr") + public R newMr(@PathVariable("workspaceId") String workspaceId, @RequestBody ApplicationMergeEntity entity) throws IOException { + T.VerifyUtil.is(entity).notNull() + .and(entity.getTitle()).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(entity.getTargetBranch()).notEmpty(RCode.PARAM_CANNOT_EMPTY) + .and(entity.getTargetBranch()).notEmpty(RCode.PARAM_CANNOT_EMPTY); + + // workspaceId + entity.setWorkspaceId(workspaceId); + + ApplicationMergeEntity record = applicationMergeService.newMr(entity); + return R.ok().putData("record", record); + } + + @PutMapping("/{workspaceId}/mr/{mrId}") + public synchronized R mergeMr(@PathVariable("mrId") String mrId, @RequestBody(required = false) Map body) { + String action = T.MapUtil.getStr(body, "action"); + T.VerifyUtil.is(action).notEmpty(RCode.PARAM_CANNOT_EMPTY); + + ApplicationMergeEntity record = applicationMergeService.mergeMr(mrId, action); + if(T.BooleanUtil.and( + T.ObjectUtil.equals("merge", action), + T.ObjectUtil.notEqual(MergeRequestStatus.MERGED.toString(), record.getStatus()) + )){ + return R.error(RCode.GIT_MERGE_FAILED).putData("record", record); + } + return R.ok().putData("record", record); + } + +} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/app/service/IApplicationMergeService.java b/src/main/java/net/geedge/asw/module/app/service/IApplicationMergeService.java index 13e515f..d3d88b7 100644 --- a/src/main/java/net/geedge/asw/module/app/service/IApplicationMergeService.java +++ b/src/main/java/net/geedge/asw/module/app/service/IApplicationMergeService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import net.geedge.asw.module.app.entity.ApplicationMergeEntity; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -13,15 +14,15 @@ public interface IApplicationMergeService extends IService params); - ApplicationMergeEntity newMr(ApplicationMergeEntity entity); + ApplicationMergeEntity newMr(ApplicationMergeEntity entity) throws IOException; - List> listMrCommit(String mrId); + List> listMrCommit(String mrId) throws IOException; Map mrCommitDiff(String mrId); - Map getMrConflictList(String mrId); + Map getMrConflictList(String mrId) throws IOException; - void resolveMrConflicts(String mrId, Map body); + void resolveMrConflicts(String mrId, Map body) throws IOException; ApplicationMergeEntity mergeMr(String mrId, String action); diff --git a/src/main/java/net/geedge/asw/module/app/service/IApplicationService.java b/src/main/java/net/geedge/asw/module/app/service/IApplicationService.java index e0bb4a3..ea1bbd9 100644 --- a/src/main/java/net/geedge/asw/module/app/service/IApplicationService.java +++ b/src/main/java/net/geedge/asw/module/app/service/IApplicationService.java @@ -4,13 +4,28 @@ import cn.hutool.json.JSONObject; import net.geedge.asw.module.app.entity.ApplicationEntity; import java.util.List; +import java.util.Map; public interface IApplicationService { - List queryList(String workspaceId, String branchName, String... names); + Map infoApplication(String workspaceId, String branch, String name); + + List listApplication(String workspaceId, String branch, String... names); + + List> listApplication(String workspaceId, String branch, String q); + + void newApplication(String workspaceId, String branch, String name); + + void deleteApplication(String workspaceId, String branch, String name); + + void updateApplication(String workspaceId, String branch, String lastCommitId, String message, List> updateContent); + + List> listApplicationCommit(String workspaceId, String branch, String name, String file); + + Map infoApplicationFileContent(String workspaceId, String branch, String name, String commitId, String file); byte[] exportAppByFormat(List appList, String format); - List importAppByFormat(String workspaceId, String branchName, String format, List dataList); + List importAppByFormat(String workspaceId, String branch, String format, List dataList); } diff --git a/src/main/java/net/geedge/asw/module/app/service/IBranchService.java b/src/main/java/net/geedge/asw/module/app/service/IBranchService.java new file mode 100644 index 0000000..862be9e --- /dev/null +++ b/src/main/java/net/geedge/asw/module/app/service/IBranchService.java @@ -0,0 +1,18 @@ +package net.geedge.asw.module.app.service; + +import java.util.List; +import java.util.Map; + +public interface IBranchService { + + List> listBranch(String workspaceId, String search); + + Map infoBranch(String workspaceId, String branch); + + Map newBranch(String workspaceId, String name, String ref); + + void deleteBranch(String workspaceId, String branch); + + List> listBranchCommit(String workspaceId, String branch, String path); + +} 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 deleted file mode 100644 index 66ce20e..0000000 --- a/src/main/java/net/geedge/asw/module/app/service/IGitService.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.geedge.asw.module.app.service; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; -import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheEntry; -import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -public interface IGitService { - - Git getGitInstance(String workspaceId); - - Repository initRepository(String workspaceId); - - Map infoCommit(String workspaceId, String commitId); - - List> listBranch(String workspaceId, String search); - - Map infoBranch(String workspaceId, String branch); - - Map newBranch(String workspaceId, String branch, String ref); - - void deleteBranch(String workspaceId, String branch); - - boolean isBranchExists(String workspaceId, String branch); - - List> listBranchCommit(String workspaceId, String branch, String path); - - String getParentCommitId(String workspaceId, String branch, String commitId); - - String getLatestCommitId(String workspaceId, String branch); - - String getMergeBase(String workspaceId, String branchA, String branchB); - - ObjectId insertBlobFileToDatabase(Repository repository, byte[] data) throws IOException; - - DirCacheEntry buildDirCacheEntry(String path, FileMode mode, ObjectId objectId); - - List> getDiffFileListInCommits(String workspaceId, String newCommitId, String oldCommitId); - - String mergeBranch(String workspaceId, String sourceBranch, String targetBranch, String message, List> resolveConflictFileContent) throws RuntimeException; - - 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); - - List> listCommitRange(String workspaceId, String newCommitId, String oldCommitId); - - List getConflictFilePathInBranches(String workspaceId, String srcBranch, String tgtBranch); - - List> getConflictFileInfoInBranches(String workspaceId, String srcBranch, String tgtBranch); - - boolean commitIndex(Repository repo, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message) throws IOException, ConcurrentRefUpdateException; - -} diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationMergeServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationMergeServiceImpl.java index 313bf65..da4cc51 100644 --- a/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationMergeServiceImpl.java +++ b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationMergeServiceImpl.java @@ -13,12 +13,20 @@ import net.geedge.asw.common.util.T; import net.geedge.asw.module.app.dao.ApplicationMergeDao; import net.geedge.asw.module.app.entity.ApplicationMergeEntity; import net.geedge.asw.module.app.service.IApplicationMergeService; -import net.geedge.asw.module.app.service.IGitService; +import net.geedge.asw.module.app.service.IBranchService; +import net.geedge.asw.module.app.util.JGitUtils; import net.geedge.asw.module.sys.entity.SysUserEntity; import net.geedge.asw.module.sys.service.ISysUserService; +import net.geedge.asw.module.workspace.service.IWorkspaceService; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -29,10 +37,13 @@ public class ApplicationMergeServiceImpl extends ServiceImpl> listMrCommit(String mrId) { + public List> listMrCommit(String mrId) throws IOException { ApplicationMergeEntity entity = this.getById(mrId); T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND); String workspaceId = entity.getWorkspaceId(); - String oldCommitId = entity.getStartCommitId(); - String newCommitId = null; - MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus()); - switch (mergeRequestStatus) { - case OPEN: { - newCommitId = gitService.getLatestCommitId(workspaceId, entity.getSourceBranch()); - break; - } - case CLOSED: - case MERGED: { - newCommitId = entity.getEndCommitId(); - break; - } - } + File gitDir = workspaceService.getGitDir(workspaceId); - // newCommitId-oldCommitId 期间的提交信息 - List> commitRange = gitService.listCommitRange(workspaceId, newCommitId, oldCommitId); - return commitRange; + try (Repository repository = JGitUtils.openRepository(gitDir)) { + String oldCommitId = entity.getStartCommitId(); + String newCommitId = null; + MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus()); + switch (mergeRequestStatus) { + case OPEN: { + newCommitId = JGitUtils.getBranchLatestCommit(repository, entity.getSourceBranch()).getName(); + break; + } + case CLOSED: + case MERGED: { + newCommitId = entity.getEndCommitId(); + break; + } + } + + // newCommitId-oldCommitId 期间的提交信息 + List revCommits = JGitUtils.listCommitRange(repository, newCommitId, oldCommitId); + + List> list = T.ListUtil.list(true); + revCommits.forEach(revCommit -> list.add(JGitUtils.buildAswCommitInfo(revCommit))); + return list; + } } @Override @@ -107,103 +129,115 @@ public class ApplicationMergeServiceImpl extends ServiceImpl conflictFileList = T.ListUtil.list(true); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + String sourceBranch = entity.getSourceBranch(); + String targetBranch = entity.getTargetBranch(); - // src branch changed files - String oldCommitId = entity.getStartCommitId(); - String newCommitId = null; - MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus()); - switch (mergeRequestStatus) { - case OPEN: { - newCommitId = gitService.getLatestCommitId(workspaceId, sourceBranch); - if (gitService.isBranchExists(workspaceId, targetBranch)) { - // open 状态下,获取 sourceBranch,targetBranch 冲突文件 - conflictFileList = gitService.getConflictFilePathInBranches(workspaceId, sourceBranch, targetBranch); - } - break; - } - case CLOSED: - case MERGED: { - newCommitId = entity.getEndCommitId(); - break; - } - } + // 获取 sourceBranch,targetBranch 合并冲突文件 + List conflictFileList = T.ListUtil.list(true); - List> diffFileListInCommits = gitService.getDiffFileListInCommits(workspaceId, newCommitId, oldCommitId); - List finalConflictFileList = conflictFileList; - diffFileListInCommits.parallelStream() - .forEach(m -> { - T.MapUtil.renameKey(m, "oldContent", "sourceContent"); - T.MapUtil.renameKey(m, "newContent", "targetContent"); - - T.MapUtil.renameKey(m, "oldPath", "sourcePath"); - T.MapUtil.renameKey(m, "newPath", "targetPath"); - - String sourcePath = T.MapUtil.getStr(m, "sourcePath"); - String targetPath = T.MapUtil.getStr(m, "targetPath"); - m.put("conflict", false); - if (finalConflictFileList.contains(sourcePath) || finalConflictFileList.contains(targetPath)) { - m.put("conflict", true); + // src branch changed files + String oldCommitId = entity.getStartCommitId(); + String newCommitId = null; + MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus()); + switch (mergeRequestStatus) { + case OPEN: { + RevCommit branchLatestCommit = JGitUtils.getBranchLatestCommit(repository, sourceBranch); + newCommitId = branchLatestCommit.getName(); + if (JGitUtils.isBranchExists(repository, targetBranch)) { + // open 状态下,获取 sourceBranch,targetBranch 冲突文件 + conflictFileList = JGitUtils.getConflictFilePathInBranches(repository, sourceBranch, targetBranch); } - }); + break; + } + case CLOSED: + case MERGED: { + newCommitId = entity.getEndCommitId(); + break; + } + } - Map sourceCommit = gitService.infoCommit(workspaceId, oldCommitId); - Map targetCommit = gitService.infoCommit(workspaceId, newCommitId); + List> diffFileListInCommits = JGitUtils.getDiffFileListInCommits(repository, newCommitId, oldCommitId); + List finalConflictFileList = conflictFileList; + diffFileListInCommits.parallelStream() + .forEach(m -> { + T.MapUtil.renameKey(m, "oldContent", "sourceContent"); + T.MapUtil.renameKey(m, "newContent", "targetContent"); - Map m = T.MapUtil.builder() - .put("sourceBranch", sourceBranch) - .put("targetBranch", targetBranch) - .put("sourceCommitId", oldCommitId) - .put("targetCommitId", newCommitId) - .put("sourceCommit", sourceCommit) - .put("targetCommit", targetCommit) - .put("files", diffFileListInCommits) - .build(); - return m; + T.MapUtil.renameKey(m, "oldPath", "sourcePath"); + T.MapUtil.renameKey(m, "newPath", "targetPath"); + + String sourcePath = T.MapUtil.getStr(m, "sourcePath"); + String targetPath = T.MapUtil.getStr(m, "targetPath"); + m.put("conflict", false); + if (finalConflictFileList.contains(sourcePath) || finalConflictFileList.contains(targetPath)) { + m.put("conflict", true); + } + }); + + RevCommit sourceCommit = JGitUtils.infoCommit(repository, oldCommitId); + RevCommit targetCommit = JGitUtils.infoCommit(repository, newCommitId); + + Map m = T.MapUtil.builder() + .put("sourceBranch", sourceBranch) + .put("targetBranch", targetBranch) + .put("sourceCommitId", oldCommitId) + .put("targetCommitId", newCommitId) + .put("sourceCommit", JGitUtils.buildAswCommitInfo(sourceCommit)) + .put("targetCommit", JGitUtils.buildAswCommitInfo(targetCommit)) + .put("files", diffFileListInCommits) + .build(); + return m; + } catch (IOException e) { + log.error(e, "[mrCommitDiff] [error] [id: {}]", mrId); + throw new RuntimeException(e); + } } @Override - public Map getMrConflictList(String mrId) { + public Map getMrConflictList(String mrId) throws IOException { ApplicationMergeEntity entity = this.getById(mrId); T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND); String workspaceId = entity.getWorkspaceId(); - String sourceBranch = entity.getSourceBranch(); - String targetBranch = entity.getTargetBranch(); + File gitDir = workspaceService.getGitDir(workspaceId); - List> conflictFileInfoInBranches = gitService.getConflictFileInfoInBranches(workspaceId, sourceBranch, targetBranch); - if (T.CollUtil.isNotEmpty(conflictFileInfoInBranches)) { - // srcBranch->tgtBranch 有冲突,先从 tgtBranch merge 到 srcBranch,在 srcBranch 处理后再合并,参考 gitlab - StringBuilder commitMessage = new StringBuilder(); - commitMessage.append("Merge branch '").append(targetBranch).append("' into '").append(sourceBranch).append("'\n\n"); - commitMessage.append("# Conflicts:\n"); - for (Map map : conflictFileInfoInBranches) { - String path = T.MapUtil.getStr(map, "path"); - commitMessage.append("# ").append(path).append("\n"); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + String sourceBranch = entity.getSourceBranch(); + String targetBranch = entity.getTargetBranch(); + + List> conflictFileInfoInBranches = JGitUtils.getConflictFileInfoInBranches(repository, sourceBranch, targetBranch); + if (T.CollUtil.isNotEmpty(conflictFileInfoInBranches)) { + // srcBranch->tgtBranch 有冲突,先从 tgtBranch merge 到 srcBranch,在 srcBranch 处理后再合并,参考 gitlab + StringBuilder commitMessage = new StringBuilder(); + commitMessage.append("Merge branch '").append(targetBranch).append("' into '").append(sourceBranch).append("'\n\n"); + commitMessage.append("# Conflicts:\n"); + for (Map map : conflictFileInfoInBranches) { + String path = T.MapUtil.getStr(map, "path"); + commitMessage.append("# ").append(path).append("\n"); + } + + String parentId = JGitUtils.getBranchLatestCommit(repository, sourceBranch).getName(); + Map m = T.MapUtil.builder() + .put("sourceBranch", sourceBranch) + .put("targetBranch", targetBranch) + .put("commitId", parentId) + .put("commitMessage", commitMessage.toString()) + .put("files", conflictFileInfoInBranches) + .build(); + return m; } - String parentId = gitService.getLatestCommitId(workspaceId, sourceBranch); - Map m = T.MapUtil.builder() + return T.MapUtil.builder() .put("sourceBranch", sourceBranch) .put("targetBranch", targetBranch) - .put("commitId", parentId) - .put("commitMessage", commitMessage.toString()) + .put("commitId", null) + .put("commitMessage", null) .put("files", conflictFileInfoInBranches) .build(); - return m; } - - return T.MapUtil.builder() - .put("sourceBranch", sourceBranch) - .put("targetBranch", targetBranch) - .put("commitId", null) - .put("commitMessage", null) - .put("files", conflictFileInfoInBranches) - .build(); } /** @@ -213,20 +247,28 @@ public class ApplicationMergeServiceImpl extends ServiceImpl body) { + public void resolveMrConflicts(String mrId, Map body) throws IOException { ApplicationMergeEntity entity = this.getById(mrId); T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND); String workspaceId = entity.getWorkspaceId(); - String sourceBranch = entity.getSourceBranch(); - String targetBranch = entity.getTargetBranch(); + File gitDir = workspaceService.getGitDir(workspaceId); - String commitId = T.MapUtil.getStr(body, "commitId"); - String commitMessage = T.MapUtil.getStr(body, "commitMessage"); - List> files = T.MapUtil.get(body, "files", List.class, new ArrayList()); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + String author = loginUserEntity.getName(); - // 反过来合并 - gitService.mergeBranch(workspaceId, targetBranch, sourceBranch, commitMessage, files); + String sourceBranch = entity.getSourceBranch(); + String targetBranch = entity.getTargetBranch(); + String commitMessage = T.MapUtil.getStr(body, "commitMessage"); + List> files = T.MapUtil.get(body, "files", List.class, new ArrayList()); + + // 反过来合并 + JGitUtils.mergeBranch(repository, targetBranch, sourceBranch, author, commitMessage, files); + } catch (GitAPIException e) { + log.error(e, "[resolveMrConflicts] [error] [id: {}]", mrId); + throw new RuntimeException(e); + } } /** @@ -241,75 +283,84 @@ public class ApplicationMergeServiceImpl extends ServiceImpl() + .eq(ApplicationMergeEntity::getId, mrId) + .set(ApplicationMergeEntity::getEndCommitId, entity.getEndCommitId()) + .set(ApplicationMergeEntity::getStatus, entity.getStatus()) + .set(ApplicationMergeEntity::getMessage, entity.getMessage()) + ); + return entity; } + case "close": { + String updateStatus = StrUtil.equals(MergeRequestStatus.OPEN.toString(), entity.getStatus()) ? MergeRequestStatus.CLOSED.toString() : entity.getStatus(); + entity.setStatus(updateStatus); - // update - this.update(new LambdaUpdateWrapper() - .eq(ApplicationMergeEntity::getId, mrId) - .set(ApplicationMergeEntity::getEndCommitId, entity.getEndCommitId()) - .set(ApplicationMergeEntity::getStatus, entity.getStatus()) - .set(ApplicationMergeEntity::getMessage, entity.getMessage()) - ); - return entity; + // 关闭请求,保留当前 src branch last commit = end_commit_id + String latestCommitId = JGitUtils.getBranchLatestCommit(repository, entity.getSourceBranch()).getName(); + this.update(new LambdaUpdateWrapper() + .eq(ApplicationMergeEntity::getId, mrId) + .set(ApplicationMergeEntity::getStatus, updateStatus) + .set(ApplicationMergeEntity::getEndCommitId, latestCommitId) + ); + return entity; + } + default: + break; } - case "close": { - String updateStatus = StrUtil.equals(MergeRequestStatus.OPEN.toString(), entity.getStatus()) ? MergeRequestStatus.CLOSED.toString() : entity.getStatus(); - entity.setStatus(updateStatus); - - // 关闭请求,保留当前 src branch last commit = end_commit_id - String latestCommitId = gitService.getLatestCommitId(entity.getWorkspaceId(), entity.getSourceBranch()); - this.update(new LambdaUpdateWrapper() - .eq(ApplicationMergeEntity::getId, mrId) - .set(ApplicationMergeEntity::getStatus, updateStatus) - .set(ApplicationMergeEntity::getEndCommitId, latestCommitId) - ); - return entity; - } - default: - break; + return entity; + } catch (IOException e) { + log.error(e, "[mergeMr] [error] [id: {}] [action: {}]", mrId, action); + throw new RuntimeException(e); } - return entity; } diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationServiceImpl.java index eb7b1a4..082425b 100644 --- a/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationServiceImpl.java @@ -1,5 +1,6 @@ package net.geedge.asw.module.app.service.impl; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.json.JSON; import cn.hutool.json.JSONConfig; import cn.hutool.json.JSONObject; @@ -9,108 +10,568 @@ import net.geedge.asw.common.util.RCode; import net.geedge.asw.common.util.T; import net.geedge.asw.module.app.entity.ApplicationEntity; import net.geedge.asw.module.app.service.IApplicationService; -import net.geedge.asw.module.app.service.IGitService; import net.geedge.asw.module.app.service.ITSGApplicationService; +import net.geedge.asw.module.app.util.JGitUtils; +import net.geedge.asw.module.sys.entity.SysUserEntity; +import net.geedge.asw.module.sys.service.ISysUserService; +import net.geedge.asw.module.workspace.service.IWorkspaceService; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; -import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.*; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.util.io.DisabledOutputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; @Service public class ApplicationServiceImpl implements IApplicationService { private static final Log log = Log.get(); + @Value("${asw.application.template.meta.json}") + private String metaJsonTemplate; + + @Value("${asw.application.template.signature.json}") + private String signatureJsonTemplate; + @Autowired - private IGitService gitService; + private ISysUserService userService; + + @Autowired + private IWorkspaceService workspaceService; @Autowired @Qualifier("tsg2402ApplicationService") private ITSGApplicationService tsg2402ApplicationService; @Override - public List queryList(String workspaceId, String branch, String... names) { - List resultList = T.ListUtil.list(true); + public Map infoApplication(String workspaceId, String branch, String name) { + Map result = T.MapUtil.builder() + .put("branch", branch) + .build(); - try (Git git = gitService.getGitInstance(workspaceId)) { - Repository repository = git.getRepository(); + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + 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)) { - ObjectId branchRef = repository.resolve(branch); treeWalk.addTree(revWalk.parseTree(branchRef)); - treeWalk.setFilter(PathFilter.create("applications/")); + treeWalk.setFilter(PathFilter.create("applications/" + name + "/")); treeWalk.setRecursive(true); - Map signatureMapping = T.MapUtil.newHashMap(); - + Map appFilePathMapping = T.MapUtil.newHashMap(true); while (treeWalk.next()) { - String filePath = treeWalk.getPathString(); + String pathString = treeWalk.getPathString(); + Map m = T.MapUtil.builder() + .put("path", pathString) + .build(); + + Map fileContent = JGitUtils.getFileContent(repository, pathString, treeWalk.getObjectId(0)); + m.putAll(fileContent); + files.add(m); + + appFilePathMapping.put(pathString, pathString); + } + + Map lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appFilePathMapping); + for (Map m : files) { + String path = T.MapUtil.getStr(m, "path"); + RevCommit revCommit = lastCommitMapping.get(path); + m.put("lastCommitId", revCommit != null ? revCommit.getName() : null); + } + } + result.put("files", files); + } catch (IOException e) { + log.error(e, "[infoApplication] [error] [workspaceId: {}] [branch: {}] [name: {}]", workspaceId, branch, name); + throw new RuntimeException(e); + } + return result; + } + + @Override + public List> listApplication(String workspaceId, String branch, String q) { + List> resultList = T.ListUtil.list(true); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + 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); + + Map appDirPathMapping = T.MapUtil.newHashMap(true); + while (treeWalk.next()) { + String filePath = treeWalk.getPathString(); + String fileName = treeWalk.getNameString(); + if (T.StrUtil.equals("meta.json", fileName)) { + // application_name 从目录中获取 String applicationName = T.PathUtil.getPathEle(Path.of(filePath), 1).toString(); - if (T.BooleanUtil.and( - T.ObjectUtil.isNotEmpty(names), - !Arrays.asList(names).contains(applicationName))) { + // filter by name + if (T.StrUtil.isNotEmpty(q) && !T.StrUtil.containsIgnoreCase(applicationName, q)) { continue; } - switch (treeWalk.getNameString()) { - case "meta.json": { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - String metaJsonStr = T.StrUtil.utf8Str(loader.getBytes()); - metaJsonStr = T.StrUtil.emptyToDefault(metaJsonStr, T.StrUtil.EMPTY_JSON); - - ApplicationEntity application = T.JSONUtil.toBean(metaJsonStr, ApplicationEntity.class); - application.setName(applicationName); - resultList.add(application); - break; - } - case "signature.json": { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - signatureMapping.put(applicationName, T.StrUtil.utf8Str(loader.getBytes())); - break; - } - default: - break; + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + String metaJsonStr = T.StrUtil.utf8Str(loader.getBytes()); + metaJsonStr = T.StrUtil.emptyToDefault(metaJsonStr, T.StrUtil.EMPTY_JSON); + Map metaJsonMap = T.MapUtil.empty(); + try { + metaJsonMap = T.JSONUtil.toBean(metaJsonStr, Map.class); + } catch (Exception e) { + log.error(e, "[listApplication] [meat.json format error] [application: {}]", applicationName); } - } - for (ApplicationEntity entity : resultList) { - String name = entity.getName(); - String signature = signatureMapping.getOrDefault(name, ""); - if (T.StrUtil.isNotEmpty(signature)) { - JSONObject jsonObject = T.JSONUtil.parseObj(signature); - entity.setSignature(jsonObject); - } + Map m = T.MapUtil.newHashMap(true); + m.putAll(metaJsonMap); + m.put("name", applicationName); + + String appDirPath = treeWalk.getPathString().replaceAll(fileName, ""); + appDirPathMapping.put(applicationName, appDirPath); + + resultList.add(m); } - } catch (IOException e) { - log.error(e, "[queryList] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch); - throw new RuntimeException(e); } + + Map lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appDirPathMapping); + + for (Map map : resultList) { + String applicationName = T.MapUtil.getStr(map, "name"); + RevCommit lastCommit = T.MapUtil.get(lastCommitMapping, applicationName, RevCommit.class); + map.put("commit", JGitUtils.buildAswCommitInfo(lastCommit)); + } + + // 按照提交日期倒叙 + resultList = resultList.stream().sorted(Comparator.comparing(map -> { + Map commit = T.MapUtil.get((Map) map, "commit", Map.class, new HashMap(2)); + return (Long) T.MapUtil.getLong(commit, "createdAt", 0l); + }).reversed()).collect(Collectors.toList()); + } catch (IOException e) { + log.error(e, "[listApplication] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch); + throw new RuntimeException(e); } return resultList; } + @Override + public List listApplication(String workspaceId, String branch, String... names) { + List resultList = T.ListUtil.list(true); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + 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); + + Map signatureMapping = T.MapUtil.newHashMap(); + + while (treeWalk.next()) { + String filePath = treeWalk.getPathString(); + String applicationName = T.PathUtil.getPathEle(Path.of(filePath), 1).toString(); + if (T.BooleanUtil.and( + T.ObjectUtil.isNotEmpty(names), + !Arrays.asList(names).contains(applicationName))) { + continue; + } + + switch (treeWalk.getNameString()) { + case "meta.json": { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + String metaJsonStr = T.StrUtil.utf8Str(loader.getBytes()); + metaJsonStr = T.StrUtil.emptyToDefault(metaJsonStr, T.StrUtil.EMPTY_JSON); + + ApplicationEntity application = T.JSONUtil.toBean(metaJsonStr, ApplicationEntity.class); + application.setName(applicationName); + resultList.add(application); + break; + } + case "signature.json": { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + signatureMapping.put(applicationName, T.StrUtil.utf8Str(loader.getBytes())); + break; + } + default: + break; + } + } + + for (ApplicationEntity entity : resultList) { + String name = entity.getName(); + String signature = signatureMapping.getOrDefault(name, ""); + if (T.StrUtil.isNotEmpty(signature)) { + JSONObject jsonObject = T.JSONUtil.parseObj(signature); + entity.setSignature(jsonObject); + } + } + } catch (IOException e) { + log.error(e, "[listApplication] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch); + throw new RuntimeException(e); + } + return resultList; + } + + @Override + public void newApplication(String workspaceId, String branch, String name) { + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + Map filePathAndBlobIdMap = T.MapUtil.newHashMap(true); + for (String str : T.ListUtil.of("README.md", "meta.json", "signature.json")) { + String savePath = T.StrUtil.concat(true, "applications/", name, "/", str); + + String fileContent = T.StrUtil.EMPTY; + if ("meta.json".equals(str)) { + JSONObject jsonObject = T.JSONUtil.parseObj(this.metaJsonTemplate); + jsonObject.set("id", T.StrUtil.uuid()); + jsonObject.set("name", name); + jsonObject.set("longName", name); + fileContent = T.JSONUtil.parse(jsonObject).toJSONString(2); + } + if ("signature.json".equals(str)) { + fileContent = this.signatureJsonTemplate; + } + + ObjectId objectId = JGitUtils.insertBlobFileToDatabase(repository, fileContent.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 = JGitUtils.buildDirCacheEntry(treeWalk.getPathString(), treeWalk.getFileMode(0), 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 = JGitUtils.buildDirCacheEntry(filePath, FileMode.REGULAR_FILE, blobId); + newTreeBuilder.add(dirCacheEntry); + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + + String message = String.format("feat: add %s application", name); + + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + PersonIdent personIdent = JGitUtils.buildPersonIdent(loginUserEntity.getName()); + + // commit + JGitUtils.createCommit(repository, branch, newTreeId, message, personIdent); + } + } catch (IOException | ConcurrentRefUpdateException e) { + log.error(e, "[newApplication] [error] [workspaceId: {}] [branch: {}] [name: {}]", workspaceId, branch, name); + throw new RuntimeException(e); + } + } + + @Override + public void deleteApplication(String workspaceId, String branch, String name) { + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + 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/", name, "/"); + while (treeWalk.next()) { + String pathString = treeWalk.getPathString(); + if (!pathString.startsWith(appFilePrefixStr)) { + DirCacheEntry entry = JGitUtils.buildDirCacheEntry(pathString, treeWalk.getFileMode(0), treeWalk.getObjectId(0)); + newTreeBuilder.add(entry); + } + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + + String message = String.format("refactor: remove %s application", name); + + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + PersonIdent personIdent = JGitUtils.buildPersonIdent(loginUserEntity.getName()); + + // commit + JGitUtils.createCommit(repository, branch, newTreeId, message, personIdent); + } catch (IOException | ConcurrentRefUpdateException e) { + log.error(e, "[deleteApplication] [error] [workspaceId: {}] [branch: {}] [name: {}]", workspaceId, branch, name); + throw new RuntimeException(e); + } + } + + @Override + public void updateApplication(String workspaceId, String branch, String lastCommitId, String message, List> updateContent) { + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + Git git = Git.open(repository.getDirectory());) { + // 分支最新 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 = JGitUtils.buildDirCacheEntry(pathString, treeWalk.getFileMode(0), 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 = JGitUtils.insertBlobFileToDatabase(repository, data); + dirCacheEntry.setObjectId(objectId); + } else { + // text + ObjectId objectId = JGitUtils.insertBlobFileToDatabase(repository, content.getBytes()); + dirCacheEntry.setObjectId(objectId); + } + newTreeBuilder.add(dirCacheEntry); + } + } + + newTreeBuilder.finish(); + ObjectId newTreeId = newTree.writeTree(inserter); + + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + PersonIdent personIdent = JGitUtils.buildPersonIdent(loginUserEntity.getName()); + + // commit + JGitUtils.createCommit(repository, branch, newTreeId, message, personIdent); + } + } catch (IOException | GitAPIException e) { + log.error(e, "[updateApplication] [error] [workspaceId: {}] [branch: {}] [lastCommitId: {}]", workspaceId, branch, lastCommitId); + throw new RuntimeException(e); + } + } + + @Override + public List> listApplicationCommit(String workspaceId, String branch, String name, String file) { + List> resultList = T.ListUtil.list(true); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + Git git = Git.open(repository.getDirectory());) { + + String filterPath = T.StrUtil.concat(true, "applications/", name); + 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(JGitUtils.buildAswCommitInfo(commit)); + } + } catch (IOException | GitAPIException e) { + log.error(e, "[listApplicationCommit] [error] [workspaceId: {}] [branch: {}] [name: {}]", workspaceId, branch, name); + throw new RuntimeException(e); + } + return resultList; + } + + @Override + public Map infoApplicationFileContent(String workspaceId, String branch, String name, String commitId, String file) { + // applications/qq/meta.json + String path = T.StrUtil.concat(true, "applications/", name, "/", file); + + Map result = T.MapUtil.builder() + .put("path", path) + .build(); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + TreeWalk treeWalk = new TreeWalk(repository); + RevWalk revWalk = new RevWalk(repository); + ) { + RevCommit revCommit = revWalk.parseCommit(ObjectId.fromString(commitId)); + ObjectId treeId = revCommit.getTree().getId(); + + treeWalk.addTree(treeId); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + if (T.StrUtil.equals(path, treeWalk.getPathString())) { + Map fileContent = JGitUtils.getFileContent(repository, treeWalk.getPathString(), treeWalk.getObjectId(0)); + result.putAll(fileContent); + } + } + } catch (IOException e) { + log.error(e, "[infoApplicationFileContent] [error] [workspaceId: {}] [branch: {}] [name: {}]", workspaceId, branch, name); + throw new RuntimeException(e); + } + return result; + } + + /** + * 通过 path 获取 lastCommit + * + * @param repository + * @param branch + * @param pathMapping + * @return + */ + public Map getLastCommitIdDataByPath(Repository repository, String branch, Map pathMapping) { + Map result = new HashMap<>(); + try (Git git = new Git(repository); + DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE); + ) { + diffFormatter.setRepository(repository); + + // 指定分支获取 log + Iterable commits = git.log() + .add(git.getRepository().resolve(branch)) + .call(); + for (RevCommit commit : commits) { + // 获取上次提交,用以对比差异 + RevCommit parent = commit.getParentCount() > 0 ? commit.getParent(0) : null; + if (parent == null) { + continue; + } + + // 计算当前提交与上次提交之间的差异 + List diffs = diffFormatter.scan(parent.getTree(), commit.getTree()); + for (DiffEntry diff : diffs) { + String newPath = diff.getNewPath(); + String oldPath = diff.getOldPath(); + + // 检查是否匹配目标路径 + for (Map.Entry entry : pathMapping.entrySet()) { + String key = entry.getKey(); + String path = entry.getValue(); + if (T.BooleanUtil.and( + (newPath.startsWith(path) || oldPath.startsWith(path)), + !result.containsKey(key) + )) { + result.put(key, commit); + } + } + } + + // 如果所有路径都找到对应的最后提交,提前结束循环 + if (result.size() == pathMapping.size()) { + break; + } + } + } catch (IOException | GitAPIException e) { + log.error(e, "[getLastCommitIdDataByPath] [workspace: {}] [branch: {}]", repository.getDirectory().getPath(), branch); + } + return result; + } + public void saveOrUpdateBatch(String workspaceId, String branch, String commitMessage, List list) { - try (Git git = gitService.getGitInstance(workspaceId); - Repository repository = git.getRepository(); + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); RevWalk revWalk = new RevWalk(repository)) { ObjectId branchRef = repository.resolve(branch); @@ -186,7 +647,7 @@ public class ApplicationServiceImpl implements IApplicationService { DirCacheEntry dirCacheEntry = new DirCacheEntry(filePath); dirCacheEntry.setFileMode(FileMode.REGULAR_FILE); - ObjectId blobId = gitService.insertBlobFileToDatabase(repository, fileContent.getBytes()); + ObjectId blobId = JGitUtils.insertBlobFileToDatabase(repository, fileContent.getBytes()); dirCacheEntry.setObjectId(blobId); newTreeBuilder.add(dirCacheEntry); } @@ -207,8 +668,8 @@ public class ApplicationServiceImpl implements IApplicationService { } String fileContent = T.JSONUtil.parse(jsonObject).toJSONString(2); - ObjectId objectId = gitService.insertBlobFileToDatabase(repository, fileContent.getBytes()); - DirCacheEntry dirCacheEntry = gitService.buildDirCacheEntry(T.StrUtil.concat(true, "applications/", entity.getName(), "/meta.json"), FileMode.REGULAR_FILE, objectId); + ObjectId objectId = JGitUtils.insertBlobFileToDatabase(repository, fileContent.getBytes()); + DirCacheEntry dirCacheEntry = JGitUtils.buildDirCacheEntry(T.StrUtil.concat(true, "applications/", entity.getName(), "/meta.json"), FileMode.REGULAR_FILE, objectId); newTreeBuilder.add(dirCacheEntry); // signature.json @@ -216,14 +677,18 @@ public class ApplicationServiceImpl implements IApplicationService { JSONObject jsonObject2 = T.JSONUtil.parseObj(jsonStr); String fileContent2 = T.JSONUtil.parse(jsonObject2).toJSONString(2); - ObjectId objectId2 = gitService.insertBlobFileToDatabase(repository, fileContent2.getBytes()); - DirCacheEntry dirCacheEntry2 = gitService.buildDirCacheEntry(T.StrUtil.concat(true, "applications/", entity.getName(), "/signature.json"), FileMode.REGULAR_FILE, objectId2); + ObjectId objectId2 = JGitUtils.insertBlobFileToDatabase(repository, fileContent2.getBytes()); + DirCacheEntry dirCacheEntry2 = JGitUtils.buildDirCacheEntry(T.StrUtil.concat(true, "applications/", entity.getName(), "/signature.json"), FileMode.REGULAR_FILE, objectId2); newTreeBuilder.add(dirCacheEntry2); } newTreeBuilder.finish(); RevCommit commitId = revWalk.parseCommit(branchRef); - boolean success = gitService.commitIndex(repository, branch, newTree, commitId, null, commitMessage); + + SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); + PersonIdent personIdent = JGitUtils.buildPersonIdent(loginUserEntity.getName()); + + boolean success = JGitUtils.commitIndex(repository, branch, newTree, commitId, null, commitMessage, personIdent); log.info("[saveOrUpdateBatch] [workspaceId: {}] [branch: {}] [commitId: {}] [success: {}]", workspaceId, branch, commitId, success); } } catch (IOException | ConcurrentRefUpdateException e) { @@ -252,7 +717,7 @@ public class ApplicationServiceImpl implements IApplicationService { } @Override - public List importAppByFormat(String workspaceId, String branchName, String format, List dataList) { + public List importAppByFormat(String workspaceId, String branch, String format, List dataList) { try { switch (format) { case "tsg2402": { @@ -260,7 +725,7 @@ public class ApplicationServiceImpl implements IApplicationService { // commit String commitMessage = "feat: import application data from TSG system (version 2402)"; - this.saveOrUpdateBatch(workspaceId, branchName, commitMessage, records); + this.saveOrUpdateBatch(workspaceId, branch, commitMessage, records); return records; } default: diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java new file mode 100644 index 0000000..42bebc9 --- /dev/null +++ b/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java @@ -0,0 +1,156 @@ +package net.geedge.asw.module.app.service.impl; + +import cn.hutool.log.Log; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +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.entity.ApplicationMergeEntity; +import net.geedge.asw.module.app.service.IBranchService; +import net.geedge.asw.module.app.service.IApplicationMergeService; +import net.geedge.asw.module.app.service.impl.ApplicationMergeServiceImpl.MergeRequestStatus; +import net.geedge.asw.module.app.util.JGitUtils; +import net.geedge.asw.module.workspace.service.IWorkspaceService; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@Service +public class BranchServiceImpl implements IBranchService { + + private final static Log log = Log.get(); + + @Autowired + private IWorkspaceService workspaceService; + + @Autowired + private IApplicationMergeService applicationMergeService; + + @Override + public List> listBranch(String workspaceId, String search) { + List> resultList = T.ListUtil.list(true); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + Git git = Git.open(repository.getDirectory())) { + String fullBranch = repository.getFullBranch(); + + String defaultBranch = "main"; + if (fullBranch != null && fullBranch.startsWith(JGitUtils.LOCAL_BRANCH_PREFIX)) { + defaultBranch = fullBranch.substring(JGitUtils.LOCAL_BRANCH_PREFIX.length()); + } + + // 默认行为,进查询本地分支 + List call = git.branchList().call(); + RevWalk revCommits = new RevWalk(repository); + + for (Ref ref : call) { + String branchName = ref.getName(); + // 返回时去掉前缀 + branchName = branchName.replaceAll(JGitUtils.LOCAL_BRANCH_PREFIX, ""); + if (T.StrUtil.isNotEmpty(search)) { + if (!T.StrUtil.contains(branchName, search)) { + continue; + } + } + + Map m = T.MapUtil.builder() + .put("name", branchName) + .put("default", T.StrUtil.equals(defaultBranch, branchName)) + .build(); + + RevCommit commit = revCommits.parseCommit(ref.getObjectId()); + m.put("commit", JGitUtils.buildAswCommitInfo(commit)); + + resultList.add(m); + } + revCommits.close(); + revCommits.dispose(); + } catch (GitAPIException | IOException e) { + log.error(e, "[listBranch] [error] [workspaceId: {}]", workspaceId); + throw new RuntimeException(e); + } + return resultList; + } + + @Override + public Map infoBranch(String workspaceId, String branchName) { + List> listBranch = this.listBranch(workspaceId, branchName); + // 分支不存在 + if (T.CollUtil.isEmpty(listBranch)) { + throw new ASWException(RCode.SYS_RECORD_NOT_FOUND); + } + return T.CollUtil.getFirst(listBranch); + } + + @Override + public Map newBranch(String workspaceId, String name, String ref) { + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + Git git = Git.open(repository.getDirectory())) { + + git.branchCreate() + .setName(name) + .setStartPoint(ref) + .call(); + + return this.infoBranch(workspaceId, name); + } catch (GitAPIException | IOException e) { + log.error(e, "[newBranch] [error] [workspaceId: {}] [branch: {}] [ref: {}]", workspaceId, name, ref); + throw new RuntimeException(e); + } + } + + @Override + public void deleteBranch(String workspaceId, String branchName) { + log.info("[deleteBranch] [begin] [workspaceId: {}] [branch: {}]", workspaceId, branchName); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir); + Git git = Git.open(repository.getDirectory())) { + + git.branchDelete() + .setBranchNames(branchName) + .setForce(true) + .call(); + + // OPEN 状态,mr 源分支被删除,mr 记录直接删掉 + applicationMergeService.remove(new LambdaQueryWrapper() + .eq(ApplicationMergeEntity::getWorkspaceId, workspaceId) + .eq(ApplicationMergeEntity::getSourceBranch, branchName) + .eq(ApplicationMergeEntity::getStatus, MergeRequestStatus.OPEN.toString()) + ); + } catch (GitAPIException | IOException e) { + log.error(e, "[deleteBranch] [error] [workspaceId: {}] [branchName: {}]", workspaceId, branchName); + throw new RuntimeException(e); + } + } + + @Override + public List> listBranchCommit(String workspaceId, String branch, String path) { + List> resultList = T.ListUtil.list(true); + + File gitDir = workspaceService.getGitDir(workspaceId); + try (Repository repository = JGitUtils.openRepository(gitDir)) { + + List branchCommitList = JGitUtils.getBranchCommitList(repository, branch, path); + branchCommitList.forEach(revCommit -> resultList.add(JGitUtils.buildAswCommitInfo(revCommit))); + + } catch (GitAPIException | IOException e) { + log.error(e, "[listBranchCommit] [error] [workspaceId: {}] [branch: {}] [path: {}]", workspaceId, branch, path); + throw new RuntimeException(e); + } + return resultList; + } + +} 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 deleted file mode 100644 index 5005f63..0000000 --- a/src/main/java/net/geedge/asw/module/app/service/impl/GitServiceImpl.java +++ /dev/null @@ -1,1397 +0,0 @@ -package net.geedge.asw.module.app.service.impl; - -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.log.Log; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -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.entity.ApplicationMergeEntity; -import net.geedge.asw.module.app.service.IApplicationMergeService; -import net.geedge.asw.module.app.service.IGitService; -import net.geedge.asw.module.app.service.impl.ApplicationMergeServiceImpl.MergeRequestStatus; -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.*; -import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.diff.*; -import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheBuilder; -import org.eclipse.jgit.dircache.DirCacheEntry; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.*; -import org.eclipse.jgit.merge.MergeFormatter; -import org.eclipse.jgit.merge.MergeStrategy; -import org.eclipse.jgit.merge.RecursiveMerger; -import org.eclipse.jgit.merge.ThreeWayMerger; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.revwalk.filter.RevFilter; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.transport.PushResult; -import org.eclipse.jgit.transport.RemoteRefUpdate; -import org.eclipse.jgit.treewalk.CanonicalTreeParser; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.PathFilter; -import org.eclipse.jgit.util.io.DisabledOutputStream; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.MessageFormat; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -@Service -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; - - @Value("${asw.application.template.meta.json}") - private String metaJsonTemplate; - - @Value("${asw.application.template.signature.json}") - private String signatureJsonTemplate; - - /** - * 本地分支引用前缀 - */ - public static final String LOCAL_BRANCH_PREFIX = "refs/heads/"; - - @Autowired - private ISysUserService userService; - - @Autowired - private IWorkspaceService workspaceService; - - @Autowired - private IApplicationMergeService applicationMergeService; - - /** - * 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}/workspace/{workspace.id} - */ - private File getRepoDirPath(String workspaceId) { - WorkspaceEntity workspace = workspaceService.getById(workspaceId); - File repoDir = T.FileUtil.file(T.WebPathUtil.getRootPath(), "workspace", workspace.getId()); - return repoDir; - } - - /** - * get git instance - */ - @Override - public Git getGitInstance(String workspaceId) { - File repoDir = this.getRepoDirPath(workspaceId); - try { - // 目录不存在,初始化裸仓库 - if (!repoDir.exists()) { - log.info("[getGitInstance] [dir not exist] [init new repository] [path: {}]", repoDir); - Repository repo = this.initRepository(workspaceId); - return new Git(repo); - } - - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - Repository repository = builder.setGitDir(repoDir) - .readEnvironment() - .findGitDir() - .build(); - - // 是否为 Git 仓库 - if (repository.getObjectDatabase().exists()) { - return new Git(repository); - } else { - log.info("[getGitInstance] [init new repository] [path: {}]", repoDir); - Repository repo = this.initRepository(workspaceId); - return new Git(repo); - } - } catch (Exception e) { - log.error(e, "[getGitInstance] [error] [path: {}]", repoDir); - throw new RuntimeException(e); - } - } - - @Override - public Repository initRepository(String workspaceId) { - File repoDir = this.getRepoDirPath(workspaceId); - try ( - Git git = Git.init() - .setBare(true) - .setDirectory(repoDir) - .setInitialBranch("main") - .call(); - Repository repository = git.getRepository(); - ObjectInserter inserter = repository.getObjectDatabase().newInserter(); - ) { - DirCache dirCache = DirCache.newInCore(); - DirCacheBuilder builder = dirCache.builder(); - - ObjectId objectId = this.insertBlobFileToDatabase(repository, "".getBytes()); - DirCacheEntry entry = this.buildDirCacheEntry("README.md", FileMode.REGULAR_FILE, objectId); - builder.add(entry); - builder.finish(); - - // commit - this.createCommit(repository, "main", dirCache.writeTree(inserter), "Initial commit"); - - return git.getRepository(); - } catch (GitAPIException | IOException e) { - log.error(e, "[initRepository] [git init error]"); - throw new RuntimeException(e); - } - } - - @Override - public Map infoCommit(String workspaceId, String commitId) { - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revCommits = new RevWalk(repository); - ) { - RevCommit commit = revCommits.parseCommit(ObjectId.fromString(commitId)); - return this.buildAswCommitInfo(commit); - } catch (IOException e) { - log.error(e, "[infoCommit] [error] [workspaceId: {}] [commitId: {}]", workspaceId, commitId); - throw new RuntimeException(e); - } - } - - @Override - public List> listBranch(String workspaceId, String search) { - List> resultList = T.ListUtil.list(true); - - try (Git git = this.getGitInstance(workspaceId)) { - Repository repository = git.getRepository(); - String fullBranch = repository.getFullBranch(); - - String defaultBranch = "main"; - if (fullBranch != null && fullBranch.startsWith(LOCAL_BRANCH_PREFIX)) { - defaultBranch = fullBranch.substring(LOCAL_BRANCH_PREFIX.length()); - } - - // 默认行为,进查询本地分支 - List call = git.branchList().call(); - RevWalk revCommits = new RevWalk(repository); - - for (Ref ref : call) { - String branchName = ref.getName(); - // 返回时去掉前缀 - branchName = branchName.replaceAll(LOCAL_BRANCH_PREFIX, ""); - if (T.StrUtil.isNotEmpty(search)) { - if (!T.StrUtil.contains(branchName, search)) { - continue; - } - } - - Map m = T.MapUtil.builder() - .put("name", branchName) - .put("default", T.StrUtil.equals(defaultBranch, branchName)) - .build(); - - RevCommit commit = revCommits.parseCommit(ref.getObjectId()); - m.put("commit", this.buildAswCommitInfo(commit)); - - resultList.add(m); - } - revCommits.close(); - revCommits.dispose(); - } catch (GitAPIException | IOException e) { - log.error(e, "[listBranch] [error] [workspaceId: {}]", workspaceId); - throw new ASWException(RCode.ERROR); - } - return resultList; - } - - @Override - public Map infoBranch(String workspaceId, String branchName) { - List> listBranch = this.listBranch(workspaceId, branchName); - - // 分支不存在 - if (T.CollUtil.isEmpty(listBranch)) { - throw new ASWException(RCode.SYS_RECORD_NOT_FOUND); - } - return T.CollUtil.getFirst(listBranch); - } - - @Override - public Map newBranch(String workspaceId, String branchName, String ref) { - try (Git git = this.getGitInstance(workspaceId)) { - git.branchCreate() - .setName(branchName) - .setStartPoint(ref) - .call(); - - return this.infoBranch(workspaceId, branchName); - } catch (GitAPIException e) { - log.error(e, "[newBranch] [error] [workspaceId: {}] [branchName: {}] [ref: {}]", workspaceId, branchName, ref); - throw new ASWException(RCode.ERROR); - } - } - - @Override - public void deleteBranch(String workspaceId, String branchName) { - log.info("[deleteBranch] [begin] [workspaceId: {}] [branch: {}]", workspaceId, branchName); - try (Git git = this.getGitInstance(workspaceId)) { - git.branchDelete() - .setBranchNames(branchName) - .setForce(true) - .call(); - - // OPEN 状态,mr 源分支被删除,mr 记录直接删掉 - applicationMergeService.remove(new LambdaQueryWrapper() - .eq(ApplicationMergeEntity::getWorkspaceId, workspaceId) - .eq(ApplicationMergeEntity::getSourceBranch, branchName) - .eq(ApplicationMergeEntity::getStatus, MergeRequestStatus.OPEN.toString()) - ); - } catch (GitAPIException e) { - log.error(e, "[deleteBranch] [error] [workspaceId: {}] [branchName: {}]", workspaceId, branchName); - throw new ASWException(RCode.ERROR); - } - } - - @Override - public boolean isBranchExists(String workspaceId, String branch) { - try (Git git = this.getGitInstance(workspaceId)) { - Ref ref = git.getRepository().findRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch)); - return null != ref; - } catch (IOException e) { - log.error(e, "[isBranchExists] [workspaceId: {}] [branch: {}]", workspaceId, branch); - throw new RuntimeException(e); - } - } - - @Override - public List> listBranchCommit(String workspaceId, String branch, String path) { - List> resultList = T.ListUtil.list(true); - try (Git git = this.getGitInstance(workspaceId)) { - ObjectId branchRef = git.getRepository().resolve(branch); - LogCommand command = git.log().add(branchRef); - if (T.StrUtil.isNotEmpty(path)) { - command.addPath(path); - } - - Iterable iterable = command.call(); - for (RevCommit commit : iterable) { - resultList.add(this.buildAswCommitInfo(commit)); - } - } catch (GitAPIException | IOException e) { - log.error(e, "[listBranchCommit] [error] [workspaceId: {}] [branch: {}] [path: {}]", workspaceId, branch, path); - throw new RuntimeException(e); - } - return resultList; - } - - @Override - public String getParentCommitId(String workspaceId, String branch, String commitId) { - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revWalk = new RevWalk(repository);) { - - Ref branchRef = repository.findRef(branch); - revWalk.markStart(revWalk.parseCommit(branchRef.getObjectId())); - - String parentCommitId = null; - Iterator iterator = revWalk.iterator(); - while (iterator.hasNext()) { - RevCommit currentCommit = iterator.next(); - if (currentCommit.getId().getName().equals(commitId)) { - if (currentCommit.getParentCount() > 0) { - parentCommitId = currentCommit.getParent(0).getName(); - } - break; - } - } - - if (T.StrUtil.isEmpty(parentCommitId)) { - throw new ASWException(RCode.GIT_PARENT_COMMITID_NOT_FOUND); - } - return parentCommitId; - } catch (IOException e) { - log.error(e, "[getParentCommitId] [error] [workspaceId: {}] [branch: {}] [commitId: {}]", workspaceId, branch, commitId); - throw new RuntimeException(e); - } - } - - @Override - public String getLatestCommitId(String workspaceId, String branch) { - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revWalk = new RevWalk(repository);) { - RevCommit commit = revWalk.parseCommit(repository.resolve(branch)); - return commit.getName(); - } catch (IOException e) { - log.error(e, "[getLatestCommitId] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch); - throw new RuntimeException(e); - } - } - - @Override - public String getMergeBase(String workspaceId, String branchA, String branchB) { - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository();) { - ObjectId branchAId = repository.resolve(branchA); - ObjectId branchBId = repository.resolve(branchB); - - return this.getMergeBase(repository, branchAId, branchBId); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * 获取 commitIdA -> commitIdB 文件差异 - * - * @param workspaceId - * @param newCommitId - * @param oldCommitId - * @return - */ - @Override - public List> getDiffFileListInCommits(String workspaceId, String newCommitId, String oldCommitId) { - log.info("[getDiffFileListInCommits] [begin] [workspaceId: {}] [newCommitId: {}] [oldCommitId: {}]", workspaceId, newCommitId, oldCommitId); - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revWalk = new RevWalk(repository); - DiffFormatter diffFormatter = new DiffFormatter(null)) { - - RevCommit oldCommit = revWalk.parseCommit(repository.resolve(oldCommitId)); - RevCommit newCommit = revWalk.parseCommit(repository.resolve(newCommitId)); - - // oldTree - CanonicalTreeParser oldTree = new CanonicalTreeParser(); - oldTree.reset(repository.newObjectReader(), oldCommit.getTree()); - // newTree - CanonicalTreeParser newTree = new CanonicalTreeParser(); - newTree.reset(repository.newObjectReader(), newCommit.getTree()); - - // diff - List> files = T.ListUtil.list(true); - - diffFormatter.setRepository(repository); - diffFormatter.setDiffComparator(RawTextComparator.DEFAULT); - diffFormatter.setDetectRenames(true); - - List diffs = diffFormatter.scan(oldTree, newTree); - for (DiffEntry diff : diffs) { - int addedLines = 0; - int deletedLines = 0; - EditList edits = diffFormatter.toFileHeader(diff).toEditList(); - for (Edit edit : edits) { - switch (edit.getType()) { - case INSERT: - addedLines += edit.getLengthB(); - break; - case DELETE: - deletedLines += edit.getLengthA(); - break; - case REPLACE: - addedLines += edit.getLengthB(); - deletedLines += edit.getLengthA(); - break; - default: - break; - } - } - - String oldPath = diff.getOldPath(), newPath = diff.getNewPath(), encoding = null, oldContent = null, newContent = null; - switch (diff.getChangeType()) { - case COPY: - case ADD: { - Map fileContent = this.getFileContent(repository, newPath, diff.getNewId().toObjectId()); - encoding = T.MapUtil.getStr(fileContent, "encoding", ""); - newContent = T.MapUtil.getStr(fileContent, "content", ""); - break; - } - case DELETE: { - Map fileContent = this.getFileContent(repository, oldPath, diff.getOldId().toObjectId()); - encoding = T.MapUtil.getStr(fileContent, "encoding", ""); - oldContent = T.MapUtil.getStr(fileContent, "content", ""); - break; - } - case MODIFY: { - Map fileContent = this.getFileContent(repository, oldPath, diff.getOldId().toObjectId()); - oldContent = T.MapUtil.getStr(fileContent, "content", ""); - - Map fileContent1 = this.getFileContent(repository, newPath, diff.getNewId().toObjectId()); - encoding = T.MapUtil.getStr(fileContent1, "encoding", ""); - newContent = T.MapUtil.getStr(fileContent1, "content", ""); - break; - } - case RENAME: { - break; - } - default: - break; - } - files.add( - T.MapUtil.builder() - .put("oldPath", oldPath) - .put("newPath", newPath) - .put("addedLines", addedLines) - .put("removedLines", deletedLines) - .put("encoding", encoding) - .put("oldContent", oldContent) - .put("newContent", newContent) - .put("action", diff.getChangeType().name().toLowerCase()) - .build() - ); - } - return files; - } catch (IOException e) { - log.error(e, "[getDiffFileListInCommits] [error] [workspaceId: {}] [newCommitId: {}] [oldCommitId: {}]", workspaceId, newCommitId, oldCommitId); - throw new RuntimeException(e); - } - } - - @Override - public String mergeBranch(String workspaceId, String srcBranch, String tgtBranch, String message, List> resolveConflictFileContent) throws RuntimeException { - log.info("[mergeBranch] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch); - // prepare a new folder for the cloned repository - File localPath = T.FileUtil.file(net.geedge.asw.common.util.Constants.TEMP_PATH, T.StrUtil.uuid()); - T.FileUtil.del(localPath); - T.FileUtil.mkdir(localPath); - - // bare repository - File repoDir = this.getRepoDirPath(workspaceId); - - // clone - try (Git git = Git.cloneRepository() - .setBare(false) - .setURI(repoDir.getAbsolutePath()) - .setDirectory(localPath) - .setCredentialsProvider(null) - .call(); - Repository repository = git.getRepository();) { - StoredConfig config = repository.getConfig(); - SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); - config.setString("user", null, "name", loginUserEntity.getName()); - config.setString("user", null, "email", "asw@geedgenetworks.com"); - config.save(); - - // git fetch - git.fetch().call(); - - // checout - git.checkout() - .setCreateBranch(T.StrUtil.equals("main", tgtBranch) ? false : true) - .setName(tgtBranch) - .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) - .setStartPoint("origin/" + tgtBranch) - .call(); - - // merge - MergeResult mergeResult = git.merge() - .setCommit(true) - .setMessage(message) - .setStrategy(MergeStrategy.RECURSIVE) - .setFastForward(MergeCommand.FastForwardMode.NO_FF) - .include(repository.findRef("origin/" + srcBranch)) - .call(); - - MergeResult.MergeStatus mergeStatus = mergeResult.getMergeStatus(); - log.info("[mergeBranch] [merge status: {}]", mergeStatus); - if (!mergeStatus.isSuccessful()) { - // 解决冲突 - if (mergeStatus == MergeResult.MergeStatus.CONFLICTING && T.CollUtil.isNotEmpty(resolveConflictFileContent)) { - Map conflicts = mergeResult.getConflicts(); - for (Map.Entry entry : conflicts.entrySet()) { - String conflictFilePath = entry.getKey(); - Map map = resolveConflictFileContent.stream() - .filter(m -> T.StrUtil.equals(conflictFilePath, T.MapUtil.getStr(m, "path"))) - .findFirst() - .orElse(null); - if (null != map) { - Path filePath = Paths.get(localPath.getAbsolutePath(), conflictFilePath); - Files.write(filePath, T.MapUtil.getStr(map, "content").getBytes(StandardCharsets.UTF_8)); - git.add().addFilepattern(conflictFilePath).call(); - } - } - git.commit().setMessage(message).call(); - } else { - // 其他类型的合并错误,抛出异常 - String errorMessage = String.format("Merge failed: %s, Conflicts: %s", mergeStatus, mergeResult.getConflicts()); - throw new RuntimeException(errorMessage); - } - } - - // push - Iterable pushResultIterable = git.push() - .setRemote("origin") - .add(tgtBranch) - .call(); - for (PushResult pushResult : pushResultIterable) { - for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) { - if (update.getStatus() != RemoteRefUpdate.Status.OK && update.getStatus() != RemoteRefUpdate.Status.UP_TO_DATE) { - log.error("[mergeBranch] [push error] [remote: {}] [status: {}]", update.getRemoteName(), update.getStatus()); - String errorMessage = "Push failed: " + update.getStatus(); - throw new RuntimeException(errorMessage); - } - } - } - - if (null != mergeResult.getNewHead()) { - return mergeResult.getNewHead().getName(); - } - // 解决冲突合并的情况下无法从 mergeResult 中获取提交信息,查询最新提交信息返回 - return this.getLatestCommitId(workspaceId, srcBranch); - } catch (Exception e) { - log.error(e, "[mergeBranch] [error] [workspaceId: {}]", workspaceId); - throw new RuntimeException(e.getMessage()); - } finally { - T.FileUtil.del(localPath); - log.info("[mergeBranch] [finshed] [workspaceId: {}]", workspaceId); - } - } - - @Override - public List> listApplication(String workspaceId, String branch, String q) { - List> resultList = T.ListUtil.list(true); - - try (Git git = this.getGitInstance(workspaceId)) { - 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); - - Map appDirPathMapping = T.MapUtil.newHashMap(true); - while (treeWalk.next()) { - String filePath = treeWalk.getPathString(); - String fileName = treeWalk.getNameString(); - if (T.StrUtil.equals("meta.json", fileName)) { - // application_name 从目录中获取 - String applicationName = T.PathUtil.getPathEle(Path.of(filePath), 1).toString(); - // filter by name - if (T.StrUtil.isNotEmpty(q) && !T.StrUtil.containsIgnoreCase(applicationName, q)) { - continue; - } - - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - String metaJsonStr = T.StrUtil.utf8Str(loader.getBytes()); - metaJsonStr = T.StrUtil.emptyToDefault(metaJsonStr, T.StrUtil.EMPTY_JSON); - Map metaJsonMap = T.MapUtil.empty(); - try { - metaJsonMap = T.JSONUtil.toBean(metaJsonStr, Map.class); - } catch (Exception e) { - log.error(e, "[listApplication] [meat.json format error] [applicationName: {}]", applicationName); - } - - Map m = T.MapUtil.newHashMap(true); - m.putAll(metaJsonMap); - m.put("name", applicationName); - - String appDirPath = treeWalk.getPathString().replaceAll(fileName, ""); - appDirPathMapping.put(applicationName, appDirPath); - - resultList.add(m); - } - } - - Map lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appDirPathMapping); - - for (Map map : resultList) { - String applicationName = T.MapUtil.getStr(map, "name"); - RevCommit lastCommit = T.MapUtil.get(lastCommitMapping, applicationName, RevCommit.class); - map.put("commit", this.buildAswCommitInfo(lastCommit)); - } - } - - // 按照提交日期倒叙 - resultList = resultList.stream().sorted(Comparator.comparing(map -> { - Map commit = T.MapUtil.get((Map) map, "commit", Map.class, new HashMap(2)); - return (Long) T.MapUtil.getLong(commit, "createdAt", 0l); - }).reversed()).collect(Collectors.toList()); - } catch (IOException 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(); - - try (Git git = this.getGitInstance(workspaceId)) { - 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); - - Map appFilePathMapping = T.MapUtil.newHashMap(true); - while (treeWalk.next()) { - String pathString = treeWalk.getPathString(); - Map m = T.MapUtil.builder() - .put("path", pathString) - .build(); - - Map fileContent = this.getFileContent(repository, pathString, treeWalk.getObjectId(0)); - m.putAll(fileContent); - files.add(m); - - appFilePathMapping.put(pathString, pathString); - } - - Map lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appFilePathMapping); - for (Map m : files) { - String path = T.MapUtil.getStr(m, "path"); - RevCommit revCommit = lastCommitMapping.get(path); - m.put("lastCommitId", revCommit != null ? revCommit.getName() : null); - } - } - result.put("files", files); - } catch (IOException 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) { - try (Git git = this.getGitInstance(workspaceId)) { - Repository repository = git.getRepository(); - - Map filePathAndBlobIdMap = T.MapUtil.newHashMap(true); - for (String str : T.ListUtil.of("README.md", "meta.json", "signature.json")) { - String savePath = T.StrUtil.concat(true, "applications/", applicationName, "/", str); - - String fileContent = T.StrUtil.EMPTY; - if ("meta.json".equals(str)) { - JSONObject jsonObject = T.JSONUtil.parseObj(this.metaJsonTemplate); - jsonObject.set("id", T.StrUtil.uuid()); - jsonObject.set("name", applicationName); - jsonObject.set("longName", applicationName); - fileContent = T.JSONUtil.parse(jsonObject).toJSONString(2); - } - if ("signature.json".equals(str)) { - fileContent = this.signatureJsonTemplate; - } - - ObjectId objectId = this.insertBlobFileToDatabase(repository, fileContent.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 = this.buildDirCacheEntry(treeWalk.getPathString(), treeWalk.getFileMode(0), 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 = this.buildDirCacheEntry(filePath, FileMode.REGULAR_FILE, 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 | ConcurrentRefUpdateException 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) { - try (Git git = this.getGitInstance(workspaceId)) { - 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 = this.buildDirCacheEntry(pathString, treeWalk.getFileMode(0), 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 | ConcurrentRefUpdateException 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) { - try (Git git = this.getGitInstance(workspaceId)) { - 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 = this.buildDirCacheEntry(pathString, treeWalk.getFileMode(0), 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); - - try (Git git = this.getGitInstance(workspaceId)) { - 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 commitId, String file) { - // applications/qq/meta.json - String path = T.StrUtil.concat(true, "applications/", applicationName, "/", file); - - Map result = T.MapUtil.builder() - .put("path", path) - .build(); - - try (Git git = this.getGitInstance(workspaceId)) { - Repository repository = git.getRepository(); - - try (TreeWalk treeWalk = new TreeWalk(repository); - RevWalk revWalk = new RevWalk(repository); - ) { - RevCommit revCommit = revWalk.parseCommit(ObjectId.fromString(commitId)); - ObjectId treeId = revCommit.getTree().getId(); - - treeWalk.addTree(treeId); - treeWalk.setRecursive(true); - - while (treeWalk.next()) { - if (T.StrUtil.equals(path, treeWalk.getPathString())) { - Map fileContent = this.getFileContent(repository, treeWalk.getPathString(), treeWalk.getObjectId(0)); - result.putAll(fileContent); - } - } - } - } catch (IOException e) { - log.error(e, "[infoApplicationFileContent] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName); - throw new ASWException(RCode.ERROR); - } - return result; - } - - /** - * commit range - * 沿着 parent(0) 的主路径提交列表,忽略所有分支路径的提交 - * - * @param workspaceId - * @param newCommitId - * @param oldCommitId - * @return - */ - public List> listCommitRange(String workspaceId, String newCommitId, String oldCommitId) { - log.info("[listCommitRange] [begin] [workspaceId: {}] [newCommitId: {}] [oldCommitId: {}]", workspaceId, newCommitId, oldCommitId); - List> commitList = T.ListUtil.list(true); - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository();) { - - try (RevWalk revWalk = new RevWalk(repository)) { - RevCommit revCommit = revWalk.parseCommit(repository.resolve(newCommitId)); - - Set visitedCommits = new HashSet<>(); - while (revCommit != null && !visitedCommits.contains(revCommit.getId())) { - if (oldCommitId != null && revCommit.getId().getName().equals(oldCommitId)) { - break; - } - - commitList.add(this.buildAswCommitInfo(revCommit)); - visitedCommits.add(revCommit.getId()); - - // 沿着 parent(0) 前进 - if (revCommit.getParentCount() > 0) { - revCommit = revWalk.parseCommit(revCommit.getParent(0)); - } else { - revCommit = null; - } - } - } catch (IOException e) { - log.error(e, "[listCommitRange] [error]"); - throw new RuntimeException(e); - } finally { - log.info("[listCommitRange] [finshed] [workspaceId: {}] [commits size: {}]", workspaceId, commitList.size()); - } - } - return commitList; - } - - /** - * 获取 srcBranch -> tgtBranch 冲突文件路径 - * srcBranch 合并到 tgtBranch - * - * @param workspaceId - * @param srcBranch - * @param tgtBranch - */ - @Override - public List getConflictFilePathInBranches(String workspaceId, String srcBranch, String tgtBranch) { - log.info("[getConflictFileListInBranches] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch); - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revWalk = new RevWalk(repository);) { - - RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch)); - RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch)); - - // Find the merge base - String mergeBaseId = this.getMergeBase(repository, commitA, commitB); - log.info("[getConflictFileListInBranches] [mergeBase: {}]", mergeBaseId); - - RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId)); - - ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - threeWayMerger.setBase(mergeBase); - - boolean isOk = threeWayMerger.merge(commitA, commitB); - log.info("[getConflictFileListInBranches] [merge result] [isOk: {}]", isOk); - if (!isOk) { - List unmergedPaths = ((RecursiveMerger) threeWayMerger).getUnmergedPaths(); - return unmergedPaths; - } - return T.ListUtil.empty(); - } catch (IOException e) { - log.error(e, "[getConflictFileListInBranches] [error]"); - throw new RuntimeException(e); - } - } - - /** - * 三路对比 srcBranch,tgtBranch,返回冲突文件详情 - * - * @param workspaceId - * @param srcBranch - * @param tgtBranch - * @return - */ - @Override - public List> getConflictFileInfoInBranches(String workspaceId, String srcBranch, String tgtBranch) { - log.info("[getConflictFileInfoInBranches] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch); - try (Git git = this.getGitInstance(workspaceId); - Repository repository = git.getRepository(); - RevWalk revWalk = new RevWalk(repository);) { - - RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch)); - RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch)); - - // Find the merge base - String mergeBaseId = this.getMergeBase(repository, commitA, commitB); - log.info("[getConflictFileInfoInBranches] [mergeBase: {}]", mergeBaseId); - - RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId)); - - ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - threeWayMerger.setBase(mergeBase); - - boolean isOk = threeWayMerger.merge(commitA, commitB); - log.info("[getConflictFileInfoInBranches] [merge result] [isOk: {}]", isOk); - - if (!isOk) { - List> files = T.ListUtil.list(true); - - Map> mergeResults = ((RecursiveMerger) threeWayMerger).getMergeResults(); - for (Map.Entry> entry : mergeResults.entrySet()) { - String unmergedPath = entry.getKey(); - // 暂不支持处理二进制文件冲突 - if (this.isBinary(T.FileNameUtil.getName(unmergedPath))) { - throw new ASWException(RCode.GIT_BINARY_CONFLICT_ERROR); - } - - org.eclipse.jgit.merge.MergeResult mergeResult = entry.getValue(); - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - MergeFormatter formatter = new MergeFormatter(); - - String oursName = unmergedPath; - String theirsName = unmergedPath; - formatter.formatMerge(outputStream, mergeResult, mergeBaseId, oursName, theirsName, StandardCharsets.UTF_8); - files.add( - T.MapUtil.builder() - .put("path", unmergedPath) - .put("content", outputStream.toString(StandardCharsets.UTF_8.name())) - .put("conflict", true) - .build() - ); - } - } - return files; - } - return T.ListUtil.empty(); - } catch (IOException e) { - log.error(e, "[getConflictFileInfoInBranches] [error]"); - throw new RuntimeException(e); - } - } - - /** - * 将 file object 添加到 objectDatabases,也就是 ./objects 目录下 - * - * @param repository - * @param data - * @return - * @throws IOException - */ - @Override - public 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; - } - } - - @Override - public DirCacheEntry buildDirCacheEntry(String path, FileMode mode, ObjectId objectId) { - DirCacheEntry dirCacheEntry = new DirCacheEntry(path); - dirCacheEntry.setFileMode(mode); - dirCacheEntry.setObjectId(objectId); - return dirCacheEntry; - } - - /** - * 根据 path,objectId 读取文件内容 - * 响应 Map,key=encoding,content - * - * @param repository - * @param path - * @param objectId - * @return - * @throws IOException - */ - private Map getFileContent(Repository repository, String path, ObjectId objectId) throws IOException { - String encoding = null, content = null; - - ObjectLoader loader = repository.open(objectId); - if (this.isBinary(T.FileNameUtil.getName(path))) { - encoding = "base64"; - content = Base64.getEncoder().encodeToString(loader.getBytes()); - } else { - content = T.StrUtil.utf8Str(loader.getBytes()); - } - return T.MapUtil.builder() - .put("encoding", encoding) - .put("content", content) - .build(); - } - - /** - * Returns the merge base of two commits or null if there is no common ancestry. - * 返回两个提交的合并基础,如果没有共同祖先,则返回 null。 - * - * @param repository - * @param commitIdA - * @param commitIdB - * @return the commit id of the merge base or null if there is no common base - */ - public String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) { - try (RevWalk rw = new RevWalk(repository)) { - RevCommit a = rw.lookupCommit(commitIdA); - RevCommit b = rw.lookupCommit(commitIdB); - - rw.setRevFilter(RevFilter.MERGE_BASE); - rw.markStart(a); - rw.markStart(b); - RevCommit mergeBase = rw.next(); - if (mergeBase == null) { - return null; - } - return mergeBase.getName(); - } catch (Exception e) { - log.error(e, "Failed to determine merge base"); - throw new RuntimeException(e); - } - } - - /** - * 通过 path 获取 lastCommit - * - * @param repository - * @param branch - * @param pathMapping - * @return - */ - public Map getLastCommitIdDataByPath(Repository repository, String branch, Map pathMapping) { - Map result = new HashMap<>(); - try (Git git = new Git(repository); - DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE); - ) { - diffFormatter.setRepository(repository); - - // 指定分支获取 log - Iterable commits = git.log() - .add(git.getRepository().resolve(branch)) - .call(); - for (RevCommit commit : commits) { - // 获取上次提交,用以对比差异 - RevCommit parent = commit.getParentCount() > 0 ? commit.getParent(0) : null; - if (parent == null) { - continue; - } - - // 计算当前提交与上次提交之间的差异 - List diffs = diffFormatter.scan(parent.getTree(), commit.getTree()); - for (DiffEntry diff : diffs) { - String newPath = diff.getNewPath(); - String oldPath = diff.getOldPath(); - - // 检查是否匹配目标路径 - for (Map.Entry entry : pathMapping.entrySet()) { - String key = entry.getKey(); - String path = entry.getValue(); - if (T.BooleanUtil.and( - (newPath.startsWith(path) || oldPath.startsWith(path)), - !result.containsKey(key) - )) { - result.put(key, commit); - } - } - } - - // 如果所有路径都找到对应的最后提交,提前结束循环 - if (result.size() == pathMapping.size()) { - break; - } - } - } catch (IOException | GitAPIException e) { - log.error(e, "[getLastCommitIdDataByPath] [workspace: {}] [branch: {}]", repository.getDirectory().getPath(), branch); - } - return result; - } - - /** - * commit - * - * @param repo - * @param branch - * @param treeId - * @param message - * @throws IOException - */ - public void createCommit(Repository repo, String branch, ObjectId treeId, String message) throws IOException, ConcurrentRefUpdateException { - 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 指向新的提交 - RefUpdate ru = null; - RefUpdate.Result rc = null; - if (null != branchRef) { - ru = repo.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch)); - ru.setNewObjectId(commitId); - rc = ru.update(); - } else { - ru = repo.updateRef(Constants.HEAD); - ru.setNewObjectId(commitId); - rc = ru.update(); - } - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: - break; - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, branch, commitId.toString(), - rc)); - } - } - } - - /** - * commit index - * - * @param repo - * @param branch - * @param index - * @param parentId1 - * @param parentId2 - * @param message - * @return - * @throws IOException - * @throws ConcurrentRefUpdateException - */ - @Override - public boolean commitIndex(Repository repo, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message) throws IOException, ConcurrentRefUpdateException { - boolean success = false; - try (ObjectInserter odi = repo.newObjectInserter()) { - - // new index - ObjectId indexTreeId = index.writeTree(odi); - - // PersonIdent - SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString()); - PersonIdent ident = new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com"); - - CommitBuilder commit = new CommitBuilder(); - commit.setAuthor(ident); - commit.setCommitter(ident); - - if (null != parentId1 && null == parentId2) { - commit.setParentId(parentId1); - } - if (null != parentId1 && null != parentId2) { - commit.setParentIds(parentId1, parentId2); - } - - commit.setTreeId(indexTreeId); - commit.setMessage(message); - - - // insert the commit into the repository - ObjectId commitId = odi.insert(commit); - odi.flush(); - - RefUpdate ru = repo.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch)); - ru.setNewObjectId(commitId); - RefUpdate.Result rc = ru.update(); - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: - success = true; - break; - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, branch, commitId.toString(), - rc)); - } - } - return success; - } - - /** - * 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; - } - -} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java b/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java new file mode 100644 index 0000000..08bd526 --- /dev/null +++ b/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java @@ -0,0 +1,820 @@ +package net.geedge.asw.module.app.util; + +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 org.apache.commons.io.FilenameUtils; +import org.eclipse.jgit.api.*; +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.diff.*; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.*; +import org.eclipse.jgit.merge.MergeFormatter; +import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.merge.RecursiveMerger; +import org.eclipse.jgit.merge.ThreeWayMerger; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Component +public class JGitUtils { + + private final static Log log = Log.get(); + /** + * 本地分支引用前缀 + */ + public static final String LOCAL_BRANCH_PREFIX = "refs/heads/"; + + /** + * 默认分支 + */ + public static final String DEFAULT_BRANCH = "main"; + + private static String textExtensions; + + @Autowired + public JGitUtils(Environment environment) { + textExtensions = environment.getProperty("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"); + } + + /** + * 返回 git repository + * + * @param gitDir + * @return + * @throws IOException + */ + public static Repository openRepository(File gitDir) throws IOException { + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + Repository repository = builder.setGitDir(gitDir) + .readEnvironment() + .findGitDir() + .build(); + return repository; + } + + /** + * 初始化仓库 + * + * @param repoDir + * @param author + */ + public static void initRepository(File repoDir, String author) { + try ( + Git git = Git.init() + .setBare(true) + .setDirectory(repoDir) + .setInitialBranch(DEFAULT_BRANCH) + .call(); + Repository repository = git.getRepository(); + ObjectInserter inserter = repository.getObjectDatabase().newInserter(); + ) { + DirCache dirCache = DirCache.newInCore(); + DirCacheBuilder builder = dirCache.builder(); + + ObjectId objectId = JGitUtils.insertBlobFileToDatabase(repository, "".getBytes()); + DirCacheEntry entry = JGitUtils.buildDirCacheEntry("README.md", FileMode.REGULAR_FILE, objectId); + builder.add(entry); + builder.finish(); + + // commit + String message = "Initial commit"; + PersonIdent personIdent = new PersonIdent(author, "asw@geedgenetworks.com"); + + JGitUtils.createCommit(repository, DEFAULT_BRANCH, dirCache.writeTree(inserter), message, personIdent); + } catch (GitAPIException | IOException e) { + log.error(e, "[initRepository] [git init error]"); + throw new RuntimeException(e); + } + } + + /** + * 返回分支是否存在 + * + * @param repository + * @param branch + * @return + * @throws IOException + */ + public static boolean isBranchExists(Repository repository, String branch) throws IOException { + Ref ref = repository.findRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch)); + return null != ref; + } + + /** + * 获取提交对象 + * + * @param repository + * @param commitId + * @return + * @throws IOException + */ + public static RevCommit infoCommit(Repository repository, String commitId) throws IOException { + try (RevWalk revCommits = new RevWalk(repository);) { + RevCommit commit = revCommits.parseCommit(ObjectId.fromString(commitId)); + return commit; + } + } + + + /** + * 返回分支最新提交 + * + * @param repository + * @param branch + * @return + */ + public static RevCommit getBranchLatestCommit(Repository repository, String branch) throws IOException { + RevWalk revWalk = new RevWalk(repository); + RevCommit commit = revWalk.parseCommit(repository.resolve(branch)); + return commit; + } + + /** + * 获取分支提交记录 + * + * @param repository + * @param branch + * @param path + * @return + * @throws GitAPIException + * @throws IOException + */ + public static List getBranchCommitList(Repository repository, String branch, String path) throws GitAPIException, IOException { + List resultList = T.ListUtil.list(true); + Git git = Git.open(repository.getDirectory()); + ObjectId branchRef = git.getRepository().resolve(branch); + LogCommand command = git.log().add(branchRef); + if (T.StrUtil.isNotEmpty(path)) { + command.addPath(path); + } + + Iterable iterable = command.call(); + for (RevCommit commit : iterable) { + resultList.add(commit); + } + return resultList; + } + + /** + * 获取父提交 + * + * @param repository + * @param branch + * @param commitId + * @return + * @throws IOException + */ + public static RevCommit getParentCommit(Repository repository, String branch, String commitId) throws IOException { + try (RevWalk revWalk = new RevWalk(repository)) { + Ref branchRef = repository.findRef(branch); + revWalk.markStart(revWalk.parseCommit(branchRef.getObjectId())); + + RevCommit parentCommit = null; + Iterator iterator = revWalk.iterator(); + while (iterator.hasNext()) { + RevCommit currentCommit = iterator.next(); + if (currentCommit.getId().getName().equals(commitId)) { + if (currentCommit.getParentCount() > 0) { + parentCommit = currentCommit.getParent(0); + } + break; + } + } + + if (null == parentCommit) { + throw new ASWException(RCode.GIT_PARENT_COMMITID_NOT_FOUND); + } + return parentCommit; + } + } + + /** + * Returns the merge base of two commits or null if there is no common ancestry. + * 返回两个提交的合并基础,如果没有共同祖先,则返回 null。 + * + * @param repository + * @param commitIdA + * @param commitIdB + * @return the commit id of the merge base or null if there is no common base + */ + public static RevCommit getMergeBaseCommit(Repository repository, ObjectId commitIdA, ObjectId commitIdB) { + try (RevWalk rw = new RevWalk(repository)) { + RevCommit a = rw.lookupCommit(commitIdA); + RevCommit b = rw.lookupCommit(commitIdB); + + rw.setRevFilter(RevFilter.MERGE_BASE); + rw.markStart(a); + rw.markStart(b); + RevCommit mergeBase = rw.next(); + if (mergeBase == null) { + return null; + } + return mergeBase; + } catch (Exception e) { + log.error(e, "Failed to determine merge base"); + throw new RuntimeException(e); + } + } + + /** + * commit range + * 沿着 parent(0) 的主路径提交列表,忽略所有分支路径的提交 + * + * @param repository + * @param newCommitId + * @param oldCommitId + * @return + * @throws IOException + */ + public static List listCommitRange(Repository repository, String newCommitId, String oldCommitId) throws IOException { + log.info("[listCommitRange] [begin] [repository: {}] [newCommitId: {}] [oldCommitId: {}]", repository, newCommitId, oldCommitId); + List commitList = T.ListUtil.list(true); + + try (RevWalk revWalk = new RevWalk(repository)) { + RevCommit revCommit = revWalk.parseCommit(repository.resolve(newCommitId)); + + Set visitedCommits = new HashSet<>(); + while (revCommit != null && !visitedCommits.contains(revCommit.getId())) { + if (oldCommitId != null && revCommit.getId().getName().equals(oldCommitId)) { + break; + } + + commitList.add(revCommit); + visitedCommits.add(revCommit.getId()); + + // 沿着 parent(0) 前进 + if (revCommit.getParentCount() > 0) { + revCommit = revWalk.parseCommit(revCommit.getParent(0)); + } else { + revCommit = null; + } + } + } finally { + log.info("[listCommitRange] [finshed] [repository: {}] [commits size: {}]", repository, commitList.size()); + } + return commitList; + } + + /** + * 构建 DirCacheEntry + * + * @param path + * @param mode + * @param objectId + * @return + */ + public static DirCacheEntry buildDirCacheEntry(String path, FileMode mode, ObjectId objectId) { + DirCacheEntry dirCacheEntry = new DirCacheEntry(path); + dirCacheEntry.setFileMode(mode); + dirCacheEntry.setObjectId(objectId); + return dirCacheEntry; + } + + public static PersonIdent buildPersonIdent(String name) { + return buildPersonIdent(name, "asw@geedgenetworks.com"); + } + + public static PersonIdent buildPersonIdent(String name, String email) { + return new PersonIdent(name, email); + } + + /** + * 将 file object 添加到 objectDatabases 并返回 ObjectId + * + * @param repository + * @param data + * @return + * @throws IOException + */ + public static 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 repository + * @param branch + * @param treeId + * @param message + * @param personIdent + * @throws IOException + * @throws ConcurrentRefUpdateException + */ + public static void createCommit(Repository repository, String branch, ObjectId treeId, String message, PersonIdent personIdent) throws IOException, ConcurrentRefUpdateException { + try (ObjectInserter inserter = repository.getObjectDatabase().newInserter(); + RevWalk revWalk = new RevWalk(repository);) { + + CommitBuilder builder = new CommitBuilder(); + builder.setTreeId(treeId); + builder.setMessage(message); + + ObjectId branchRef = repository.resolve(branch); + if (null != branchRef) { + RevCommit parentId = revWalk.parseCommit(branchRef); + builder.setParentId(parentId); + } + + builder.setAuthor(personIdent); + builder.setCommitter(personIdent); + + // 插入新的提交对象 + ObjectId commitId = inserter.insert(builder); + inserter.flush(); + + // 更新 branch 指向新的提交 + RefUpdate ru = null; + RefUpdate.Result rc = null; + if (null != branchRef) { + ru = repository.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch)); + ru.setNewObjectId(commitId); + rc = ru.update(); + } else { + ru = repository.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + rc = ru.update(); + } + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, branch, commitId.toString(), + rc)); + } + } + } + + /** + * commit index + * + * @param repository + * @param branch + * @param index + * @param parentId1 + * @param parentId2 + * @param message + * @param personIdent + * @return + * @throws IOException + * @throws ConcurrentRefUpdateException + */ + public static boolean commitIndex(Repository repository, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message, PersonIdent personIdent) throws IOException, ConcurrentRefUpdateException { + boolean success = false; + try (ObjectInserter odi = repository.newObjectInserter()) { + + // new index + ObjectId indexTreeId = index.writeTree(odi); + + // PersonIdent + CommitBuilder commit = new CommitBuilder(); + commit.setAuthor(personIdent); + commit.setCommitter(personIdent); + + if (null != parentId1 && null == parentId2) { + commit.setParentId(parentId1); + } + if (null != parentId1 && null != parentId2) { + commit.setParentIds(parentId1, parentId2); + } + + commit.setTreeId(indexTreeId); + commit.setMessage(message); + + + // insert the commit into the repository + ObjectId commitId = odi.insert(commit); + odi.flush(); + + RefUpdate ru = repository.updateRef(T.StrUtil.concat(true, JGitUtils.LOCAL_BRANCH_PREFIX, branch)); + ru.setNewObjectId(commitId); + RefUpdate.Result rc = ru.update(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + success = true; + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, branch, commitId.toString(), + rc)); + } + } + return success; + } + + + /** + * 获取 commitIdA -> commitIdB 文件差异 + * + * @param repository + * @param newCommitId + * @param oldCommitId + * @return + * @throws IOException + */ + public static List> getDiffFileListInCommits(Repository repository, String newCommitId, String oldCommitId) throws IOException { + log.info("[getDiffFileListInCommits] [begin] [repository: {}] [newCommitId: {}] [oldCommitId: {}]", repository, newCommitId, oldCommitId); + try (RevWalk revWalk = new RevWalk(repository); + DiffFormatter diffFormatter = new DiffFormatter(null); + ) { + + RevCommit oldCommit = revWalk.parseCommit(repository.resolve(oldCommitId)); + RevCommit newCommit = revWalk.parseCommit(repository.resolve(newCommitId)); + + // oldTree + CanonicalTreeParser oldTree = new CanonicalTreeParser(); + oldTree.reset(repository.newObjectReader(), oldCommit.getTree()); + // newTree + CanonicalTreeParser newTree = new CanonicalTreeParser(); + newTree.reset(repository.newObjectReader(), newCommit.getTree()); + + // diff + List> files = T.ListUtil.list(true); + + diffFormatter.setRepository(repository); + diffFormatter.setDiffComparator(RawTextComparator.DEFAULT); + diffFormatter.setDetectRenames(true); + + List diffs = diffFormatter.scan(oldTree, newTree); + for (DiffEntry diff : diffs) { + int addedLines = 0; + int deletedLines = 0; + EditList edits = diffFormatter.toFileHeader(diff).toEditList(); + for (Edit edit : edits) { + switch (edit.getType()) { + case INSERT: + addedLines += edit.getLengthB(); + break; + case DELETE: + deletedLines += edit.getLengthA(); + break; + case REPLACE: + addedLines += edit.getLengthB(); + deletedLines += edit.getLengthA(); + break; + default: + break; + } + } + + String oldPath = diff.getOldPath(), newPath = diff.getNewPath(), encoding = null, oldContent = null, newContent = null; + switch (diff.getChangeType()) { + case COPY: + case ADD: { + Map fileContent = getFileContent(repository, newPath, diff.getNewId().toObjectId()); + encoding = T.MapUtil.getStr(fileContent, "encoding", ""); + newContent = T.MapUtil.getStr(fileContent, "content", ""); + break; + } + case DELETE: { + Map fileContent = getFileContent(repository, oldPath, diff.getOldId().toObjectId()); + encoding = T.MapUtil.getStr(fileContent, "encoding", ""); + oldContent = T.MapUtil.getStr(fileContent, "content", ""); + break; + } + case MODIFY: { + Map fileContent = getFileContent(repository, oldPath, diff.getOldId().toObjectId()); + oldContent = T.MapUtil.getStr(fileContent, "content", ""); + + Map fileContent1 = getFileContent(repository, newPath, diff.getNewId().toObjectId()); + encoding = T.MapUtil.getStr(fileContent1, "encoding", ""); + newContent = T.MapUtil.getStr(fileContent1, "content", ""); + break; + } + case RENAME: { + break; + } + default: + break; + } + files.add( + T.MapUtil.builder() + .put("oldPath", oldPath) + .put("newPath", newPath) + .put("addedLines", addedLines) + .put("removedLines", deletedLines) + .put("encoding", encoding) + .put("oldContent", oldContent) + .put("newContent", newContent) + .put("action", diff.getChangeType().name().toLowerCase()) + .build() + ); + } + return files; + } + } + + + /** + * 获取 sourceBranch,targetBranch 冲突文件路径 + * + * @param repository + * @param srcBranch + * @param tgtBranch + * @return + * @throws IOException + */ + public static List getConflictFilePathInBranches(Repository repository, String srcBranch, String tgtBranch) throws IOException { + log.info("[getConflictFileListInBranches] [begin] [repository: {}] [srcBranch: {}] [tgtBranch: {}]", repository, srcBranch, tgtBranch); + try (RevWalk revWalk = new RevWalk(repository)) { + + RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch)); + RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch)); + + // Find the merge base + RevCommit mergeBaseCommit = JGitUtils.getMergeBaseCommit(repository, commitA, commitB); + String mergeBaseId = mergeBaseCommit.getName(); + log.info("[getConflictFileListInBranches] [mergeBase: {}]", mergeBaseId); + + RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId)); + + ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); + threeWayMerger.setBase(mergeBase); + + boolean isOk = threeWayMerger.merge(commitA, commitB); + log.info("[getConflictFileListInBranches] [merge result] [isOk: {}]", isOk); + if (!isOk) { + List unmergedPaths = ((RecursiveMerger) threeWayMerger).getUnmergedPaths(); + return unmergedPaths; + } + return T.ListUtil.empty(); + } + } + + /** + * 获取 sourceBranch,targetBranch 冲突文件详情 + * + * @param repository + * @param srcBranch + * @param tgtBranch + * @return + * @throws IOException + */ + public static List> getConflictFileInfoInBranches(Repository repository, String srcBranch, String tgtBranch) throws IOException { + log.info("[getConflictFileInfoInBranches] [begin] [repository: {}] [srcBranch: {}] [tgtBranch: {}]", repository, srcBranch, tgtBranch); + try (RevWalk revWalk = new RevWalk(repository);) { + + RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch)); + RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch)); + + // Find the merge base + String mergeBaseId = getMergeBaseCommit(repository, commitA, commitB).getName(); + log.info("[getConflictFileInfoInBranches] [mergeBase: {}]", mergeBaseId); + + RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId)); + + ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); + threeWayMerger.setBase(mergeBase); + + boolean isOk = threeWayMerger.merge(commitA, commitB); + log.info("[getConflictFileInfoInBranches] [merge result] [isOk: {}]", isOk); + + if (!isOk) { + List> files = T.ListUtil.list(true); + + Map> mergeResults = ((RecursiveMerger) threeWayMerger).getMergeResults(); + for (Map.Entry> entry : mergeResults.entrySet()) { + String unmergedPath = entry.getKey(); + // 暂不支持处理二进制文件冲突 + if (isBinary(T.FileNameUtil.getName(unmergedPath))) { + throw new ASWException(RCode.GIT_BINARY_CONFLICT_ERROR); + } + + org.eclipse.jgit.merge.MergeResult mergeResult = entry.getValue(); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + MergeFormatter formatter = new MergeFormatter(); + + String oursName = unmergedPath; + String theirsName = unmergedPath; + formatter.formatMerge(outputStream, mergeResult, mergeBaseId, oursName, theirsName, StandardCharsets.UTF_8); + files.add( + T.MapUtil.builder() + .put("path", unmergedPath) + .put("content", outputStream.toString(StandardCharsets.UTF_8.name())) + .put("conflict", true) + .build() + ); + } + } + return files; + } + return T.ListUtil.empty(); + } + } + + /** + * 分支合并 + * + * @param centralRepository + * @param srcBranch + * @param tgtBranch + * @param author + * @param message + * @param resolveConflictFileContent + * @throws RuntimeException + * @throws GitAPIException + * @throws IOException + */ + public static void mergeBranch(Repository centralRepository, String srcBranch, String tgtBranch, String author, String message, List> resolveConflictFileContent) throws RuntimeException, GitAPIException, IOException { + log.info("[mergeBranch] [begin] [repository: {}] [srcBranch: {}] [tgtBranch: {}]", centralRepository, srcBranch, tgtBranch); + // prepare a new folder for the cloned repository + File localPath = T.FileUtil.file(net.geedge.asw.common.util.Constants.TEMP_PATH, T.StrUtil.uuid()); + T.FileUtil.del(localPath); + T.FileUtil.mkdir(localPath); + + // bare repository + File repoDir = centralRepository.getDirectory(); + + // clone + try (Git git = Git.cloneRepository() + .setBare(false) + .setURI(repoDir.getAbsolutePath()) + .setDirectory(localPath) + .setCredentialsProvider(null) + .call(); + Repository repository = git.getRepository();) { + StoredConfig config = repository.getConfig(); + config.setString("user", null, "name", author); + config.setString("user", null, "email", "asw@geedgenetworks.com"); + config.save(); + + // git fetch + git.fetch().call(); + + // checout + git.checkout() + .setCreateBranch(T.StrUtil.equals(DEFAULT_BRANCH, tgtBranch) ? false : true) + .setName(tgtBranch) + .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) + .setStartPoint("origin/" + tgtBranch) + .call(); + + // merge + MergeResult mergeResult = git.merge() + .setCommit(true) + .setMessage(message) + .setStrategy(MergeStrategy.RECURSIVE) + .setFastForward(MergeCommand.FastForwardMode.NO_FF) + .include(repository.findRef("origin/" + srcBranch)) + .call(); + + MergeResult.MergeStatus mergeStatus = mergeResult.getMergeStatus(); + log.info("[mergeBranch] [merge status: {}]", mergeStatus); + if (!mergeStatus.isSuccessful()) { + // 解决冲突 + if (mergeStatus == MergeResult.MergeStatus.CONFLICTING && T.CollUtil.isNotEmpty(resolveConflictFileContent)) { + Map conflicts = mergeResult.getConflicts(); + for (Map.Entry entry : conflicts.entrySet()) { + String conflictFilePath = entry.getKey(); + Map map = resolveConflictFileContent.stream() + .filter(m -> T.StrUtil.equals(conflictFilePath, T.MapUtil.getStr(m, "path"))) + .findFirst() + .orElse(null); + if (null != map) { + Path filePath = Paths.get(localPath.getAbsolutePath(), conflictFilePath); + Files.write(filePath, T.MapUtil.getStr(map, "content").getBytes(StandardCharsets.UTF_8)); + git.add().addFilepattern(conflictFilePath).call(); + } + } + git.commit().setMessage(message).call(); + } else { + // 其他类型的合并错误,抛出异常 + String errorMessage = String.format("Merge failed: %s, Conflicts: %s", mergeStatus, mergeResult.getConflicts()); + throw new RuntimeException(errorMessage); + } + } + + // push + Iterable pushResultIterable = git.push() + .setRemote("origin") + .add(tgtBranch) + .call(); + for (PushResult pushResult : pushResultIterable) { + for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) { + if (update.getStatus() != RemoteRefUpdate.Status.OK && update.getStatus() != RemoteRefUpdate.Status.UP_TO_DATE) { + log.error("[mergeBranch] [push error] [remote: {}] [status: {}]", update.getRemoteName(), update.getStatus()); + String errorMessage = "Push failed: " + update.getStatus(); + throw new RuntimeException(errorMessage); + } + } + } + } finally { + T.FileUtil.del(localPath); + } + } + + /** + * build asw commit info + * + * @param commit + * @return + */ + public static 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; + } + + + /** + * is binary file + * + * @param filename + * @return + */ + public static boolean isBinary(String filename) { + String extension = FilenameUtils.getExtension(filename); + List split = T.StrUtil.split(textExtensions, ","); + return !split.contains(extension.toLowerCase()); + } + + /** + * 根据 path,objectId 读取文件内容 + * 响应 Map,key=encoding,content + * + * @param repository + * @param path + * @param objectId + * @return + * @throws IOException + */ + public static Map getFileContent(Repository repository, String path, ObjectId objectId) throws IOException { + String encoding = null, content = null; + + ObjectLoader loader = repository.open(objectId); + if (isBinary(T.FileNameUtil.getName(path))) { + encoding = "base64"; + content = Base64.getEncoder().encodeToString(loader.getBytes()); + } else { + content = T.StrUtil.utf8Str(loader.getBytes()); + } + return T.MapUtil.builder() + .put("encoding", encoding) + .put("content", content) + .build(); + } + +} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java b/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java index 9d1024f..4bb1c43 100644 --- a/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java +++ b/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java @@ -5,9 +5,10 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import net.geedge.asw.module.sys.entity.SysRoleEntity; +import net.geedge.asw.common.util.T; import net.geedge.asw.module.sys.entity.SysUserEntity; +import java.io.File; import java.util.List; @Data @@ -36,4 +37,8 @@ public class WorkspaceEntity { @TableField(exist = false) private List members; + public File buildGitPath() { + return T.FileUtil.file(T.WebPathUtil.getRootPath(), "workspace", this.getId()); + } + } \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/workspace/service/IWorkspaceService.java b/src/main/java/net/geedge/asw/module/workspace/service/IWorkspaceService.java index b1a3ec0..c263560 100644 --- a/src/main/java/net/geedge/asw/module/workspace/service/IWorkspaceService.java +++ b/src/main/java/net/geedge/asw/module/workspace/service/IWorkspaceService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import net.geedge.asw.module.workspace.entity.WorkspaceEntity; +import java.io.File; import java.util.Map; public interface IWorkspaceService extends IService{ @@ -15,4 +16,7 @@ public interface IWorkspaceService extends IService{ WorkspaceEntity updateWorkspace(WorkspaceEntity workspace); void deleteWorkspace(String ids); + + File getGitDir(String workspaceId); + } \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java b/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java index ebef0ac..e1df771 100644 --- a/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java +++ b/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java @@ -14,10 +14,12 @@ import net.geedge.asw.common.util.ASWException; import net.geedge.asw.common.util.Constants; import net.geedge.asw.common.util.RCode; import net.geedge.asw.common.util.T; +import net.geedge.asw.module.app.util.JGitUtils; import net.geedge.asw.module.feign.client.DashboardClient; import net.geedge.asw.module.feign.client.KibanaClient; +import net.geedge.asw.module.sys.entity.SysUserEntity; import net.geedge.asw.module.sys.service.ISysConfigService; -import net.geedge.asw.module.app.service.IGitService; +import net.geedge.asw.module.sys.service.ISysUserService; import net.geedge.asw.module.workspace.dao.WorkspaceDao; import net.geedge.asw.module.workspace.entity.WorkspaceEntity; import net.geedge.asw.module.workspace.entity.WorkspaceMemberEntity; @@ -37,9 +39,6 @@ public class WorkspaceServiceImpl extends ServiceImpl params) { Page page = new Query(WorkspaceEntity.class).getPage(params); @@ -96,8 +98,8 @@ public class WorkspaceServiceImpl extends ServiceImpl