refactor: application 模块代码重构,将部分 jgit 代码拆分开

This commit is contained in:
shizhendong
2024-11-20 09:58:05 +08:00
parent ec4c58db4e
commit 30b3f1c101
16 changed files with 2108 additions and 1993 deletions

View File

@@ -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<Object, Object> 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<Map<Object, Object>> 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<String, String> 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<String, Object> 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<Map<String, String>> files = T.MapUtil.get(body, "files", List.class, T.ListUtil.list(true));
if (T.CollUtil.isEmpty(files)) {
return R.ok();
}
for (Map<String, String> 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<Map<Object, Object>> 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<Object, Object> 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<ApplicationEntity> appList = applicationService.queryList(workspaceId, branchName, T.StrUtil.splitToArray(names, ","));
List<ApplicationEntity> 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);
}
}
}

View File

@@ -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<Map<Object, Object>> 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<Object, Object> record = branchService.infoBranch(workspaceId, branchName);
return R.ok().putData("record", record);
}
@PostMapping("/{workspaceId}/branch")
public synchronized R newBranch(@PathVariable("workspaceId") String workspaceId, @RequestBody Map<String, String> 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<Object, Object> 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<Map<Object, Object>> 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<Map<Object, Object>> diffList = JGitUtils.getDiffFileListInCommits(repository, newCommitId, oldCommitId);
Map<Object, Object> 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);
}
}
}

View File

@@ -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<Map<Object, Object>> 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<Object, Object> record = gitService.infoBranch(workspaceId, branchName);
return R.ok().putData("record", record);
}
@PostMapping("/{workspaceId}/branch")
public R newBranch(@PathVariable("workspaceId") String workspaceId, @RequestBody Map<String, String> 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<Object, Object> 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<Map<Object, Object>> 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<Object, Object> oldCommit = gitService.infoCommit(workspaceId, oldCommitId);
Map<Object, Object> newCommit = gitService.infoCommit(workspaceId, newCommitId);
List<Map<Object, Object>> diffList = gitService.getDiffFileListInCommits(workspaceId, newCommitId, oldCommitId);
Map<Object, Object> 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<Map<Object, Object>> 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<Object, Object> 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<String, String> 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<String, Object> 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<Map<String, String>> files = T.MapUtil.get(body, "files", List.class, T.ListUtil.list(true));
if (T.CollUtil.isEmpty(files)) {
return R.ok();
}
for (Map<String, String> 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<Map<Object, Object>> 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<Object, Object> 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<String, Object> 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<Map<Object, Object>> records = applicationMergeService.listMrCommit(mrId);
return R.ok().putData("records", records);
}
@GetMapping("/{workspaceId}/mr/{mrId}/diffs")
public R mrDiff(@PathVariable("mrId") String mrId) {
Map<Object, Object> record = applicationMergeService.mrCommitDiff(mrId);
return R.ok().putData("record", record);
}
@GetMapping("/{workspaceId}/mr/{mrId}/conflicts")
public R getMrConflictList(@PathVariable("mrId") String mrId) {
Map<Object, Object> record = applicationMergeService.getMrConflictList(mrId);
return R.ok().putData("record", record);
}
@PostMapping("/{workspaceId}/mr/{mrId}/conflicts")
public R resolveMrConflicts(@PathVariable("mrId") String mrId, @RequestBody Map<String, Object> body) {
String commitId = T.MapUtil.getStr(body, "commitId");
String commitMessage = T.MapUtil.getStr(body, "commitMessage");
List<Map<String, String>> 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<String, String> 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<String, Object> 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);
}
}

View File

@@ -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<String, Object> 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<Map<Object, Object>> records = applicationMergeService.listMrCommit(mrId);
return R.ok().putData("records", records);
}
@GetMapping("/{workspaceId}/mr/{mrId}/diffs")
public R mrDiff(@PathVariable("mrId") String mrId) {
Map<Object, Object> 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<Object, Object> record = applicationMergeService.getMrConflictList(mrId);
return R.ok().putData("record", record);
}
@PostMapping("/{workspaceId}/mr/{mrId}/conflicts")
public R resolveMrConflicts(@PathVariable("mrId") String mrId, @RequestBody Map<String, Object> body) throws IOException{
String commitId = T.MapUtil.getStr(body, "commitId");
String commitMessage = T.MapUtil.getStr(body, "commitMessage");
List<Map<String, String>> 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<String, String> 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<String, Object> 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);
}
}

View File

@@ -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<ApplicationMergeEntit
Page queryList(Map<String, Object> params);
ApplicationMergeEntity newMr(ApplicationMergeEntity entity);
ApplicationMergeEntity newMr(ApplicationMergeEntity entity) throws IOException;
List<Map<Object, Object>> listMrCommit(String mrId);
List<Map<Object, Object>> listMrCommit(String mrId) throws IOException;
Map<Object, Object> mrCommitDiff(String mrId);
Map<Object, Object> getMrConflictList(String mrId);
Map<Object, Object> getMrConflictList(String mrId) throws IOException;
void resolveMrConflicts(String mrId, Map<String, Object> body);
void resolveMrConflicts(String mrId, Map<String, Object> body) throws IOException;
ApplicationMergeEntity mergeMr(String mrId, String action);

View File

@@ -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<ApplicationEntity> queryList(String workspaceId, String branchName, String... names);
Map<Object, Object> infoApplication(String workspaceId, String branch, String name);
List<ApplicationEntity> listApplication(String workspaceId, String branch, String... names);
List<Map<Object, Object>> 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<Map<String, String>> updateContent);
List<Map<Object, Object>> listApplicationCommit(String workspaceId, String branch, String name, String file);
Map<Object, Object> infoApplicationFileContent(String workspaceId, String branch, String name, String commitId, String file);
byte[] exportAppByFormat(List<ApplicationEntity> appList, String format);
List<ApplicationEntity> importAppByFormat(String workspaceId, String branchName, String format, List<JSONObject> dataList);
List<ApplicationEntity> importAppByFormat(String workspaceId, String branch, String format, List<JSONObject> dataList);
}

View File

@@ -0,0 +1,18 @@
package net.geedge.asw.module.app.service;
import java.util.List;
import java.util.Map;
public interface IBranchService {
List<Map<Object, Object>> listBranch(String workspaceId, String search);
Map<Object, Object> infoBranch(String workspaceId, String branch);
Map<Object, Object> newBranch(String workspaceId, String name, String ref);
void deleteBranch(String workspaceId, String branch);
List<Map<Object, Object>> listBranchCommit(String workspaceId, String branch, String path);
}

View File

@@ -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<Object, Object> infoCommit(String workspaceId, String commitId);
List<Map<Object, Object>> listBranch(String workspaceId, String search);
Map<Object, Object> infoBranch(String workspaceId, String branch);
Map<Object, Object> newBranch(String workspaceId, String branch, String ref);
void deleteBranch(String workspaceId, String branch);
boolean isBranchExists(String workspaceId, String branch);
List<Map<Object, Object>> 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<Map<Object, Object>> getDiffFileListInCommits(String workspaceId, String newCommitId, String oldCommitId);
String mergeBranch(String workspaceId, String sourceBranch, String targetBranch, String message, List<Map<String, String>> resolveConflictFileContent) throws RuntimeException;
List<Map<Object, Object>> listApplication(String workspaceId, String branch, String q);
Map<Object, Object> 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<Map<String, String>> updateContent);
List<Map<Object, Object>> listApplicationCommit(String workspaceId, String branch, String applicationName, String file);
Map<Object, Object> infoApplicationFileContent(String workspaceId, String branch, String applicationName, String commitId, String file);
List<Map<Object, Object>> listCommitRange(String workspaceId, String newCommitId, String oldCommitId);
List<String> getConflictFilePathInBranches(String workspaceId, String srcBranch, String tgtBranch);
List<Map<Object, Object>> getConflictFileInfoInBranches(String workspaceId, String srcBranch, String tgtBranch);
boolean commitIndex(Repository repo, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message) throws IOException, ConcurrentRefUpdateException;
}

View File

@@ -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<ApplicationMergeDao
private final static Log log = Log.get();
@Autowired
private IGitService gitService;
private ISysUserService userService;
@Autowired
private ISysUserService userService;
private IBranchService branchService;
@Autowired
private IWorkspaceService workspaceService;
@Override
public ApplicationMergeEntity queryInfo(String mrId) {
@@ -53,7 +64,7 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
}
@Override
public ApplicationMergeEntity newMr(ApplicationMergeEntity entity) {
public ApplicationMergeEntity newMr(ApplicationMergeEntity entity) throws IOException {
entity.setCreateTimestamp(System.currentTimeMillis());
entity.setCreateUserId(StpUtil.getLoginIdAsString());
@@ -62,10 +73,14 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
// start_commit_id
String workspaceId = entity.getWorkspaceId();
String sourceBranch = entity.getSourceBranch();
String targetBranch = entity.getTargetBranch();
String mergeBase = gitService.getMergeBase(workspaceId, sourceBranch, targetBranch);
entity.setStartCommitId(mergeBase);
File gitDir = workspaceService.getGitDir(workspaceId);
try (Repository repository = JGitUtils.openRepository(gitDir)) {
ObjectId branchAId = repository.resolve(entity.getSourceBranch());
ObjectId branchBId = repository.resolve(entity.getTargetBranch());
RevCommit mergeBaseCommit = JGitUtils.getMergeBaseCommit(repository, branchAId, branchBId);
entity.setStartCommitId(mergeBaseCommit.getName());
}
// save
this.save(entity);
@@ -76,29 +91,36 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
}
@Override
public List<Map<Object, Object>> listMrCommit(String mrId) {
public List<Map<Object, Object>> 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<Map<Object, Object>> 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<RevCommit> revCommits = JGitUtils.listCommitRange(repository, newCommitId, oldCommitId);
List<Map<Object, Object>> 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<ApplicationMergeDao
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);
// 获取 sourceBranch,targetBranch 合并冲突文件
List<String> 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<String> conflictFileList = T.ListUtil.list(true);
List<Map<Object, Object>> diffFileListInCommits = gitService.getDiffFileListInCommits(workspaceId, newCommitId, oldCommitId);
List<String> 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<Object, Object> sourceCommit = gitService.infoCommit(workspaceId, oldCommitId);
Map<Object, Object> targetCommit = gitService.infoCommit(workspaceId, newCommitId);
List<Map<Object, Object>> diffFileListInCommits = JGitUtils.getDiffFileListInCommits(repository, newCommitId, oldCommitId);
List<String> finalConflictFileList = conflictFileList;
diffFileListInCommits.parallelStream()
.forEach(m -> {
T.MapUtil.renameKey(m, "oldContent", "sourceContent");
T.MapUtil.renameKey(m, "newContent", "targetContent");
Map<Object, Object> 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<Object, Object> 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<Object, Object> getMrConflictList(String mrId) {
public Map<Object, Object> 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<Map<Object, Object>> 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<Object, Object> 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<Map<Object, Object>> 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<Object, Object> map : conflictFileInfoInBranches) {
String path = T.MapUtil.getStr(map, "path");
commitMessage.append("# ").append(path).append("\n");
}
String parentId = JGitUtils.getBranchLatestCommit(repository, sourceBranch).getName();
Map<Object, Object> 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<Object, Object> 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<ApplicationMergeDao
* @param body
*/
@Override
public void resolveMrConflicts(String mrId, Map<String, Object> body) {
public void resolveMrConflicts(String mrId, Map<String, Object> 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<Map<String, String>> 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<Map<String, String>> 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<ApplicationMergeDao
ApplicationMergeEntity entity = this.getById(mrId);
T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND);
switch (action) {
case "merge": {
MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus());
if (!mergeRequestStatus.canMerge()) {
throw new ASWException(RCode.GIT_MERGE_NOT_SUPPORTED.setParam(mergeRequestStatus.toString()));
}
String workspaceId = entity.getWorkspaceId();
File gitDir = workspaceService.getGitDir(workspaceId);
String srcBranch = entity.getSourceBranch();
String tgtBranch = entity.getTargetBranch();
StringBuilder commitMessage = new StringBuilder();
commitMessage.append("Merge branch '").append(srcBranch).append("' into '").append(tgtBranch).append("'\n\n");
commitMessage.append(entity.getTitle());
commitMessage.append("\n");
String workspaceId = entity.getWorkspaceId();
// 检查 tgtBranch 是否存在
if (T.BooleanUtil.negate(gitService.isBranchExists(workspaceId, tgtBranch))) {
throw new ASWException(RCode.GIT_MERGE_TARGET_BRANCH_NOT_EXIST.setParam(tgtBranch));
}
// merge
try {
gitService.mergeBranch(workspaceId, srcBranch, tgtBranch, commitMessage.toString(), null);
String latestCommitId = gitService.getLatestCommitId(workspaceId, srcBranch);
entity.setEndCommitId(latestCommitId);
entity.setStatus(MergeRequestStatus.MERGED.toString());
} catch (Exception e) {
log.error(e, "[mergeMr] [merge error] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}] [msg: {}]", workspaceId, srcBranch, tgtBranch, e.getMessage());
throw new ASWException(RCode.GIT_MERGE_FAILED.setParam(e.getMessage()));
}
// remove source branch
if (1 == entity.getRemoveSourceBranch()) {
try {
log.info("[mergeMr] [remove source branch] [workspaceId: {}] [branch: {}]", workspaceId, srcBranch);
gitService.deleteBranch(workspaceId, srcBranch);
} catch (Exception e) {
log.error(e, "[mergeMr] [remove source branch error] [workspaceId: {}] [branch: {}] [msg: {}]", workspaceId, srcBranch, e.getMessage());
try (Repository repository = JGitUtils.openRepository(gitDir)) {
switch (action) {
case "merge": {
MergeRequestStatus mergeRequestStatus = MergeRequestStatus.getInstance(entity.getStatus());
if (!mergeRequestStatus.canMerge()) {
throw new ASWException(RCode.GIT_MERGE_NOT_SUPPORTED.setParam(mergeRequestStatus.toString()));
}
String srcBranch = entity.getSourceBranch();
String tgtBranch = entity.getTargetBranch();
StringBuilder commitMessage = new StringBuilder();
commitMessage.append("Merge branch '").append(srcBranch).append("' into '").append(tgtBranch).append("'\n\n");
commitMessage.append(entity.getTitle());
commitMessage.append("\n");
// 检查 tgtBranch 是否存在
if (T.BooleanUtil.negate(JGitUtils.isBranchExists(repository, tgtBranch))) {
throw new ASWException(RCode.GIT_MERGE_TARGET_BRANCH_NOT_EXIST.setParam(tgtBranch));
}
// merge
try {
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
JGitUtils.mergeBranch(repository, srcBranch, tgtBranch, loginUserEntity.getName(), commitMessage.toString(), null);
String latestCommitId = JGitUtils.getBranchLatestCommit(repository, srcBranch).getName();
entity.setEndCommitId(latestCommitId);
entity.setStatus(MergeRequestStatus.MERGED.toString());
} catch (Exception e) {
log.error(e, "[mergeMr] [merge error] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}] [msg: {}]", workspaceId, srcBranch, tgtBranch, e.getMessage());
throw new ASWException(RCode.GIT_MERGE_FAILED.setParam(e.getMessage()));
}
// remove source branch
if (1 == entity.getRemoveSourceBranch()) {
try {
log.info("[mergeMr] [remove source branch] [workspaceId: {}] [branch: {}]", workspaceId, srcBranch);
branchService.deleteBranch(workspaceId, srcBranch);
} catch (Exception e) {
log.error(e, "[mergeMr] [remove source branch error] [workspaceId: {}] [branch: {}] [msg: {}]", workspaceId, srcBranch, e.getMessage());
}
}
// update
this.update(new LambdaUpdateWrapper<ApplicationMergeEntity>()
.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<ApplicationMergeEntity>()
.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<ApplicationMergeEntity>()
.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<ApplicationMergeEntity>()
.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;
}

View File

@@ -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<ApplicationEntity> queryList(String workspaceId, String branch, String... names) {
List<ApplicationEntity> resultList = T.ListUtil.list(true);
public Map<Object, Object> infoApplication(String workspaceId, String branch, String name) {
Map<Object, Object> 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<Map<Object, Object>> 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<String, String> signatureMapping = T.MapUtil.newHashMap();
Map<String, String> appFilePathMapping = T.MapUtil.newHashMap(true);
while (treeWalk.next()) {
String filePath = treeWalk.getPathString();
String pathString = treeWalk.getPathString();
Map<Object, Object> m = T.MapUtil.builder()
.put("path", pathString)
.build();
Map<Object, Object> fileContent = JGitUtils.getFileContent(repository, pathString, treeWalk.getObjectId(0));
m.putAll(fileContent);
files.add(m);
appFilePathMapping.put(pathString, pathString);
}
Map<String, RevCommit> lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appFilePathMapping);
for (Map<Object, Object> 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<Map<Object, Object>> listApplication(String workspaceId, String branch, String q) {
List<Map<Object, Object>> 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<String, String> 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<Object, Object> 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<String, RevCommit> lastCommitMapping = this.getLastCommitIdDataByPath(repository, branch, appDirPathMapping);
for (Map<Object, Object> 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<ApplicationEntity> listApplication(String workspaceId, String branch, String... names) {
List<ApplicationEntity> 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<String, String> 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<String, ObjectId> 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<String, ObjectId> 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<Map<String, String>> 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<String> 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<DiffEntry> diffs = git.diff()
.setOldTree(c1)
.setNewTree(c2)
.call();
List<String> 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<String> 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<String> 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<String, String> 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<Map<Object, Object>> listApplicationCommit(String workspaceId, String branch, String name, String file) {
List<Map<Object, Object>> 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<RevCommit> 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<Object, Object> 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<Object, Object> 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<Object, Object> 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<String, RevCommit> getLastCommitIdDataByPath(Repository repository, String branch, Map<String, String> pathMapping) {
Map<String, RevCommit> result = new HashMap<>();
try (Git git = new Git(repository);
DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE);
) {
diffFormatter.setRepository(repository);
// 指定分支获取 log
Iterable<RevCommit> 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<DiffEntry> diffs = diffFormatter.scan(parent.getTree(), commit.getTree());
for (DiffEntry diff : diffs) {
String newPath = diff.getNewPath();
String oldPath = diff.getOldPath();
// 检查是否匹配目标路径
for (Map.Entry<String, String> 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<ApplicationEntity> 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<ApplicationEntity> importAppByFormat(String workspaceId, String branchName, String format, List<JSONObject> dataList) {
public List<ApplicationEntity> importAppByFormat(String workspaceId, String branch, String format, List<JSONObject> 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:

View File

@@ -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<Map<Object, Object>> listBranch(String workspaceId, String search) {
List<Map<Object, Object>> 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<Ref> 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<Object, Object> 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<Object, Object> infoBranch(String workspaceId, String branchName) {
List<Map<Object, Object>> 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<Object, Object> 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<ApplicationMergeEntity>()
.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<Map<Object, Object>> listBranchCommit(String workspaceId, String branch, String path) {
List<Map<Object, Object>> resultList = T.ListUtil.list(true);
File gitDir = workspaceService.getGitDir(workspaceId);
try (Repository repository = JGitUtils.openRepository(gitDir)) {
List<RevCommit> 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;
}
}

View File

@@ -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<RevCommit> getBranchCommitList(Repository repository, String branch, String path) throws GitAPIException, IOException {
List<RevCommit> 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<RevCommit> 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<RevCommit> 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<RevCommit> listCommitRange(Repository repository, String newCommitId, String oldCommitId) throws IOException {
log.info("[listCommitRange] [begin] [repository: {}] [newCommitId: {}] [oldCommitId: {}]", repository, newCommitId, oldCommitId);
List<RevCommit> commitList = T.ListUtil.list(true);
try (RevWalk revWalk = new RevWalk(repository)) {
RevCommit revCommit = revWalk.parseCommit(repository.resolve(newCommitId));
Set<ObjectId> 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<Map<Object, Object>> 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<Map<Object, Object>> files = T.ListUtil.list(true);
diffFormatter.setRepository(repository);
diffFormatter.setDiffComparator(RawTextComparator.DEFAULT);
diffFormatter.setDetectRenames(true);
List<DiffEntry> 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<Object, Object> fileContent = getFileContent(repository, newPath, diff.getNewId().toObjectId());
encoding = T.MapUtil.getStr(fileContent, "encoding", "");
newContent = T.MapUtil.getStr(fileContent, "content", "");
break;
}
case DELETE: {
Map<Object, Object> fileContent = getFileContent(repository, oldPath, diff.getOldId().toObjectId());
encoding = T.MapUtil.getStr(fileContent, "encoding", "");
oldContent = T.MapUtil.getStr(fileContent, "content", "");
break;
}
case MODIFY: {
Map<Object, Object> fileContent = getFileContent(repository, oldPath, diff.getOldId().toObjectId());
oldContent = T.MapUtil.getStr(fileContent, "content", "");
Map<Object, Object> 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<String> 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<String> unmergedPaths = ((RecursiveMerger) threeWayMerger).getUnmergedPaths();
return unmergedPaths;
}
return T.ListUtil.empty();
}
}
/**
* 获取 sourceBranch,targetBranch 冲突文件详情
*
* @param repository
* @param srcBranch
* @param tgtBranch
* @return
* @throws IOException
*/
public static List<Map<Object, Object>> 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<Map<Object, Object>> files = T.ListUtil.list(true);
Map<String, org.eclipse.jgit.merge.MergeResult<? extends Sequence>> mergeResults = ((RecursiveMerger) threeWayMerger).getMergeResults();
for (Map.Entry<String, org.eclipse.jgit.merge.MergeResult<? extends Sequence>> 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<? extends Sequence> 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<Map<String, String>> 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<String, int[][]> conflicts = mergeResult.getConflicts();
for (Map.Entry<String, int[][]> entry : conflicts.entrySet()) {
String conflictFilePath = entry.getKey();
Map<String, String> 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<PushResult> 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<Object, Object> buildAswCommitInfo(RevCommit commit) {
if (null == commit) {
return T.MapUtil.newHashMap();
}
Map<Object, Object> 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<String> 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<String> 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<Object, Object> 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();
}
}

View File

@@ -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<WorkspaceMemberEntity> members;
public File buildGitPath() {
return T.FileUtil.file(T.WebPathUtil.getRootPath(), "workspace", this.getId());
}
}

View File

@@ -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<WorkspaceEntity>{
@@ -15,4 +16,7 @@ public interface IWorkspaceService extends IService<WorkspaceEntity>{
WorkspaceEntity updateWorkspace(WorkspaceEntity workspace);
void deleteWorkspace(String ids);
File getGitDir(String workspaceId);
}

View File

@@ -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<WorkspaceDao, WorkspaceEnt
private final static Log log = Log.get();
@Autowired
private IGitService gitService;
@Autowired
private IWorkspaceService workspaceService;
@@ -55,6 +54,9 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
@Autowired
private ISysConfigService sysConfigService;
@Autowired
private ISysUserService userService;
@Override
public Page queryList(Map<String, Object> params) {
Page page = new Query(WorkspaceEntity.class).getPage(params);
@@ -96,8 +98,8 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
}
// init repository
gitService.initRepository(workspace.getId());
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
JGitUtils.initRepository(workspace.buildGitPath(), loginUserEntity.getName());
return workspace;
}
@@ -235,6 +237,24 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
}
}
/**
* get repository path
* path= {webRootPath}/workspace/{workspace.id}
*/
@Override
public File getGitDir(String workspaceId) {
WorkspaceEntity workspace = workspaceService.getById(workspaceId);
File gitDir = workspace.buildGitPath();
// 目录不存在,初始化裸仓库
if (!gitDir.exists()) {
log.info("[getGitDir] [dir not exist] [init new repository] [path: {}]", gitDir);
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
JGitUtils.initRepository(gitDir, loginUserEntity.getName());
}
return gitDir;
}
private void validateWorkspaceInfo(WorkspaceEntity workspace, boolean isUpdate) {
if (!T.StrUtil.equalsAnyIgnoreCase(workspace.getVisibility(), "private", "public")) {