feat: ASW-123 新增 application MR diff,conflict,commit 接口
1. 参考 gitlab 实现 merge request 功能
This commit is contained in:
@@ -62,6 +62,8 @@ public enum RCode {
|
||||
// GIT
|
||||
GIT_COMMIT_CONFLICT_ERROR(203001, "Commit failed; fix conflicts and then commit the result"),
|
||||
GIT_MERGE_FAILED(203002, "Merge failed"),
|
||||
GIT_PARENT_COMMITID_NOT_FOUND(203003, "Parent commitId not found"),
|
||||
GIT_BINARY_CONFLICT_ERROR(203004, "Binary file conflict found; resolve conflicts in binary files manually"),
|
||||
|
||||
|
||||
// Runner
|
||||
|
||||
@@ -11,6 +11,7 @@ import net.geedge.asw.module.app.service.IGitService;
|
||||
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;
|
||||
@@ -56,6 +57,46 @@ public class GitController {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@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 commitId,
|
||||
@PathVariable(value = "oldCommitId", required = false) String oldCommitId) {
|
||||
|
||||
if (T.StrUtil.isEmpty(oldCommitId)) {
|
||||
// oldCommitId 为空默认对比 parent commitId
|
||||
oldCommitId = gitService.getParentCommitId(workspaceId, branchName, commitId);
|
||||
}
|
||||
|
||||
List<Map<Object, Object>> diffList = gitService.getDiffFileListInCommits(workspaceId, commitId, oldCommitId);
|
||||
Map<Object, Object> record = T.MapUtil.builder()
|
||||
.put("oldBranch", branchName)
|
||||
.put("newBranch", branchName)
|
||||
.put("oldCommitId", oldCommitId)
|
||||
.put("newCommitId", commitId)
|
||||
.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,
|
||||
@@ -153,8 +194,52 @@ public class GitController {
|
||||
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 synchronized R newMr(@PathVariable("workspaceId") String workspaceId, @RequestBody ApplicationMergeEntity entity) {
|
||||
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)
|
||||
@@ -164,6 +249,12 @@ public class GitController {
|
||||
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) {
|
||||
ApplicationMergeEntity record = applicationMergeService.mergeMr(mrId);
|
||||
if (T.StrUtil.equals("success", record.getStatus())) {
|
||||
return R.ok().putData("record", record);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,25 @@ 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.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IApplicationMergeService extends IService<ApplicationMergeEntity> {
|
||||
|
||||
ApplicationMergeEntity queryInfo(String mrId);
|
||||
|
||||
Page queryList(Map<String, Object> params);
|
||||
|
||||
ApplicationMergeEntity newMr(ApplicationMergeEntity entity);
|
||||
|
||||
List<Map<Object, Object>> listMrCommit(String mrId);
|
||||
|
||||
Map<Object, Object> mrCommitDiff(String mrId);
|
||||
|
||||
Map<Object, Object> getMrConflictList(String mrId);
|
||||
|
||||
void resolveMrConflicts(String mrId, Map<String, Object> body);
|
||||
|
||||
ApplicationMergeEntity mergeMr(String mrId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package net.geedge.asw.module.app.service;
|
||||
|
||||
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
|
||||
import org.eclipse.jgit.dircache.DirCache;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -13,11 +17,21 @@ public interface IGitService {
|
||||
|
||||
List<Map<Object, Object>> listBranch(String workspaceId, String search);
|
||||
|
||||
Map<Object, Object> infoBranch(String workspaceId, String branchName);
|
||||
Map<Object, Object> infoBranch(String workspaceId, String branch);
|
||||
|
||||
Map<Object, Object> newBranch(String workspaceId, String branchName, String ref);
|
||||
Map<Object, Object> newBranch(String workspaceId, String branch, String ref);
|
||||
|
||||
void deleteBranch(String workspaceId, String branchName);
|
||||
void deleteBranch(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);
|
||||
|
||||
List<Map<Object, Object>> getDiffFileListInCommits(String workspaceId, String commitId, String oldCommitId);
|
||||
|
||||
List<Map<Object, Object>> getDiffFileListInBranches(String workspaceId, String srcBranch, String tgtBranch);
|
||||
|
||||
String mergeBranch(String workspaceId, String sourceBranch, String targetBranch, String message) throws RuntimeException;
|
||||
|
||||
@@ -35,4 +49,14 @@ public interface IGitService {
|
||||
|
||||
Map<Object, Object> infoApplicationFileContent(String workspaceId, String branch, String applicationName, String commitId, String file);
|
||||
|
||||
List<Map<Object, Object>> listCommitAfterMergeBase(String workspaceId, String srcBranch, String tgtBranch);
|
||||
|
||||
List<String> getConflictFilePathInBranches(String workspaceId, String srcBranch, String tgtBranch);
|
||||
|
||||
List<Map<Object, Object>> getConflictFileInfoInBranches(String workspaceId, String srcBranch, String tgtBranch);
|
||||
|
||||
void resolveMrConflicts(String workspaceId, String srcBranch, String tgtBranch, String commitId, String commitMessage, List<Map<String, String>> files);
|
||||
|
||||
boolean commitIndex(Repository repo, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message) throws IOException, ConcurrentRefUpdateException;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package net.geedge.asw.module.app.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import net.geedge.asw.common.config.Query;
|
||||
import net.geedge.asw.common.util.RCode;
|
||||
import net.geedge.asw.common.util.T;
|
||||
import net.geedge.asw.module.app.dao.ApplicationMergeDao;
|
||||
import net.geedge.asw.module.app.entity.ApplicationMergeEntity;
|
||||
@@ -15,6 +17,7 @@ import net.geedge.asw.module.sys.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -29,6 +32,25 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Override
|
||||
public ApplicationMergeEntity queryInfo(String mrId) {
|
||||
ApplicationMergeEntity entity = this.getById(mrId);
|
||||
T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND);
|
||||
|
||||
String commitId = entity.getCommitId();
|
||||
if (T.StrUtil.isNotEmpty(commitId)) {
|
||||
try {
|
||||
Map<Object, Object> infoCommit = gitService.infoCommit(entity.getWorkspaceId(), commitId);
|
||||
entity.setCommit(infoCommit);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
SysUserEntity user = userService.getById(entity.getCreateUserId());
|
||||
entity.setCreateUser(user);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page queryList(Map<String, Object> params) {
|
||||
Page page = new Query(ApplicationMergeEntity.class).getPage(params);
|
||||
@@ -51,23 +73,155 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
|
||||
|
||||
@Override
|
||||
public ApplicationMergeEntity newMr(ApplicationMergeEntity entity) {
|
||||
entity.setCreateTimestamp(System.currentTimeMillis());
|
||||
entity.setCreateUserId(StpUtil.getLoginIdAsString());
|
||||
|
||||
// save
|
||||
this.save(entity);
|
||||
|
||||
SysUserEntity user = userService.getById(entity.getCreateUserId());
|
||||
entity.setCreateUser(user);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<Object, Object>> listMrCommit(String mrId) {
|
||||
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();
|
||||
|
||||
// 获取 sourceBranch targetBranch 共同祖先之后, sourceBranch 的提交记录
|
||||
List<Map<Object, Object>> listCommitAfterMergeBase = gitService.listCommitAfterMergeBase(workspaceId, sourceBranch, targetBranch);
|
||||
return listCommitAfterMergeBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> mrCommitDiff(String mrId) {
|
||||
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();
|
||||
|
||||
// 获取 sourceBranch,targetBranch 合并冲突文件
|
||||
List<String> conflictFileList = gitService.getConflictFilePathInBranches(workspaceId, sourceBranch, targetBranch);
|
||||
|
||||
|
||||
// 获取 sourceBranch,targetBranch 文件差异
|
||||
String commitA = gitService.getLatestCommitId(workspaceId, sourceBranch);
|
||||
String commitB = gitService.getLatestCommitId(workspaceId, targetBranch);
|
||||
|
||||
List<Map<Object, Object>> diffFileListInCommits = gitService.getDiffFileListInCommits(workspaceId, commitA, commitB);
|
||||
diffFileListInCommits.parallelStream()
|
||||
.forEach(m -> {
|
||||
T.MapUtil.renameKey(m, "oldContent", "sourceContent");
|
||||
T.MapUtil.renameKey(m, "newContent", "targetContent");
|
||||
|
||||
String path = T.MapUtil.getStr(m, "path");
|
||||
m.put("conflict", false);
|
||||
if (conflictFileList.contains(path)) {
|
||||
m.put("conflict", true);
|
||||
}
|
||||
});
|
||||
|
||||
Map<Object, Object> m = T.MapUtil.builder()
|
||||
.put("sourceBranch", sourceBranch)
|
||||
.put("targetBranch", targetBranch)
|
||||
.put("sourceCommitId", commitA)
|
||||
.put("targetCommitId", commitB)
|
||||
.put("files", diffFileListInCommits)
|
||||
.build();
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> getMrConflictList(String mrId) {
|
||||
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();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
String parentId = gitService.getLatestCommitId(workspaceId, sourceBranch);
|
||||
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;
|
||||
}
|
||||
|
||||
return T.MapUtil.builder()
|
||||
.put("sourceBranch", sourceBranch)
|
||||
.put("targetBranch", targetBranch)
|
||||
.put("commitId", null)
|
||||
.put("commitMessage", null)
|
||||
.put("files", conflictFileInfoInBranches)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve mr conflicts
|
||||
*
|
||||
* @param mrId
|
||||
* @param body
|
||||
*/
|
||||
@Override
|
||||
public void resolveMrConflicts(String mrId, Map<String, Object> body) {
|
||||
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();
|
||||
|
||||
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());
|
||||
|
||||
gitService.resolveMrConflicts(workspaceId, sourceBranch, targetBranch, commitId, commitMessage, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* merge operation
|
||||
*
|
||||
* @param mrId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ApplicationMergeEntity mergeMr(String mrId) {
|
||||
ApplicationMergeEntity entity = this.getById(mrId);
|
||||
T.VerifyUtil.is(entity).notEmpty(RCode.SYS_RECORD_NOT_FOUND);
|
||||
|
||||
String srcBranch = entity.getSourceBranch();
|
||||
String tgtBranch = entity.getTargetBranch();
|
||||
|
||||
String message = entity.getTitle();
|
||||
String workspaceId = entity.getWorkspaceId();
|
||||
|
||||
entity.setCreateTimestamp(System.currentTimeMillis());
|
||||
entity.setCreateUserId(StpUtil.getLoginIdAsString());
|
||||
|
||||
// merge
|
||||
try {
|
||||
String commitId = gitService.mergeBranch(workspaceId, srcBranch, tgtBranch, message);
|
||||
entity.setCommitId(commitId);
|
||||
entity.setStatus("success");
|
||||
|
||||
Map<Object, Object> infoCommit = gitService.infoCommit(workspaceId, commitId);
|
||||
entity.setCommit(infoCommit);
|
||||
} catch (Exception e) {
|
||||
log.error(e, "[newMr] [merge error] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}] [msg: {}]", workspaceId, srcBranch, tgtBranch, e.getMessage());
|
||||
entity.setCommitId("");
|
||||
@@ -85,12 +239,13 @@ public class ApplicationMergeServiceImpl extends ServiceImpl<ApplicationMergeDao
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
this.save(entity);
|
||||
|
||||
SysUserEntity user = userService.getById(entity.getCreateUserId());
|
||||
entity.setCreateUser(user);
|
||||
|
||||
// update
|
||||
this.update(new LambdaUpdateWrapper<ApplicationMergeEntity>()
|
||||
.eq(ApplicationMergeEntity::getId, mrId)
|
||||
.set(ApplicationMergeEntity::getCommitId, entity.getCommitId())
|
||||
.set(ApplicationMergeEntity::getStatus, entity.getStatus())
|
||||
.set(ApplicationMergeEntity::getMessage, entity.getMessage())
|
||||
);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,19 +12,24 @@ import net.geedge.asw.module.sys.service.ISysUserService;
|
||||
import net.geedge.asw.module.workspace.entity.WorkspaceEntity;
|
||||
import net.geedge.asw.module.workspace.service.IWorkspaceService;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.eclipse.jgit.api.CreateBranchCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.MergeCommand;
|
||||
import org.eclipse.jgit.api.MergeResult;
|
||||
import org.eclipse.jgit.api.*;
|
||||
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
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.RevSort;
|
||||
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;
|
||||
@@ -36,8 +41,11 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -213,6 +221,8 @@ public class GitServiceImpl implements IGitService {
|
||||
|
||||
resultList.add(m);
|
||||
}
|
||||
revCommits.close();
|
||||
revCommits.dispose();
|
||||
} catch (GitAPIException | IOException e) {
|
||||
log.error(e, "[listBranch] [error] [workspaceId: {}]", workspaceId);
|
||||
throw new ASWException(RCode.ERROR);
|
||||
@@ -259,6 +269,198 @@ public class GitServiceImpl implements IGitService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<Object, Object>> listBranchCommit(String workspaceId, String branch, String path) {
|
||||
List<Map<Object, Object>> resultList = T.ListUtil.list(true);
|
||||
try (Git git = this.getGitInstance(workspaceId)) {
|
||||
ObjectId branchRef = git.getRepository().resolve(branch);
|
||||
LogCommand command = git.log().add(branchRef);
|
||||
if (T.StrUtil.isNotEmpty(path)) {
|
||||
command.addPath(path);
|
||||
}
|
||||
|
||||
Iterable<RevCommit> iterable = command.call();
|
||||
for (RevCommit commit : iterable) {
|
||||
resultList.add(this.buildAswCommitInfo(commit));
|
||||
}
|
||||
} catch (GitAPIException | IOException e) {
|
||||
log.error(e, "[listBranchCommit] [error] [workspaceId: {}] [branch: {}] [path: {}]", workspaceId, branch, path);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentCommitId(String workspaceId, String branch, String commitId) {
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);) {
|
||||
|
||||
Ref branchRef = repository.findRef(branch);
|
||||
revWalk.markStart(revWalk.parseCommit(branchRef.getObjectId()));
|
||||
|
||||
String parentCommitId = null;
|
||||
Iterator<RevCommit> iterator = revWalk.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
RevCommit currentCommit = iterator.next();
|
||||
if (currentCommit.getId().getName().equals(commitId)) {
|
||||
if (iterator.hasNext()) {
|
||||
parentCommitId = iterator.next().getName();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (T.StrUtil.isEmpty(parentCommitId)) {
|
||||
throw new ASWException(RCode.GIT_PARENT_COMMITID_NOT_FOUND);
|
||||
}
|
||||
return parentCommitId;
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getParentCommitId] [error] [workspaceId: {}] [branch: {}] [commitId: {}]", workspaceId, branch, commitId);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestCommitId(String workspaceId, String branch) {
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);) {
|
||||
RevCommit commit = revWalk.parseCommit(repository.resolve(branch));
|
||||
return commit.getName();
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getLatestCommitId] [error] [workspaceId: {}] [branch: {}]", workspaceId, branch);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 commitIdA -> commitIdB 文件差异
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param commitIdA
|
||||
* @param commitIdB
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<Map<Object, Object>> getDiffFileListInCommits(String workspaceId, String commitIdA, String commitIdB) {
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);
|
||||
DiffFormatter diffFormatter = new DiffFormatter(null)) {
|
||||
|
||||
CanonicalTreeParser oldTree = new CanonicalTreeParser();
|
||||
oldTree.reset(repository.newObjectReader(), revWalk.parseCommit(repository.resolve(commitIdB)).getTree());
|
||||
|
||||
CanonicalTreeParser newTree = new CanonicalTreeParser();
|
||||
newTree.reset(repository.newObjectReader(), revWalk.parseCommit(repository.resolve(commitIdA)).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 path = null, encoding = null, oldContent = null, newContent = null;
|
||||
switch (diff.getChangeType()) {
|
||||
case ADD: {
|
||||
path = diff.getNewPath();
|
||||
Map<Object, Object> fileContent = this.getFileContent(repository, path, diff.getNewId().toObjectId());
|
||||
encoding = T.MapUtil.getStr(fileContent, "encoding", "");
|
||||
newContent = T.MapUtil.getStr(fileContent, "content", "");
|
||||
break;
|
||||
}
|
||||
case DELETE: {
|
||||
path = diff.getOldPath();
|
||||
Map<Object, Object> fileContent = this.getFileContent(repository, path, diff.getOldId().toObjectId());
|
||||
encoding = T.MapUtil.getStr(fileContent, "encoding", "");
|
||||
oldContent = T.MapUtil.getStr(fileContent, "content", "");
|
||||
break;
|
||||
}
|
||||
case MODIFY: {
|
||||
path = diff.getOldPath();
|
||||
Map<Object, Object> fileContent = this.getFileContent(repository, path, diff.getOldId().toObjectId());
|
||||
oldContent = T.MapUtil.getStr(fileContent, "content", "");
|
||||
|
||||
Map<Object, Object> fileContent1 = this.getFileContent(repository, path, diff.getNewId().toObjectId());
|
||||
encoding = T.MapUtil.getStr(fileContent1, "encoding", "");
|
||||
newContent = T.MapUtil.getStr(fileContent1, "content", "");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
files.add(
|
||||
T.MapUtil.builder()
|
||||
.put("path", path)
|
||||
.put("addedLines", (addedLines == 0 ? 0 : ++addedLines))
|
||||
.put("removedLines", (deletedLines == 0 ? 0 : ++deletedLines))
|
||||
.put("encoding", encoding)
|
||||
.put("oldContent", oldContent)
|
||||
.put("newContent", newContent)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
return files;
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getDiffFileListInCommits] [error] [workspaceId: {}] [commitId: {}] [oldCommitId: {}]", workspaceId, commitIdA, commitIdB);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 branchA -> branchB 文件差异
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param branchA
|
||||
* @param branchB
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<Map<Object, Object>> getDiffFileListInBranches(String workspaceId, String branchA, String branchB) {
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);) {
|
||||
|
||||
RevCommit commitA = revWalk.parseCommit(repository.resolve(branchA));
|
||||
RevCommit commitB = revWalk.parseCommit(repository.resolve(branchB));
|
||||
|
||||
List<Map<Object, Object>> diffFileListInCommits = this.getDiffFileListInCommits(workspaceId, commitA.getName(), commitB.getName());
|
||||
diffFileListInCommits.parallelStream()
|
||||
.forEach(m -> {
|
||||
T.MapUtil.renameKey(m, "oldContent", "sourceContent");
|
||||
T.MapUtil.renameKey(m, "newContent", "targetContent");
|
||||
});
|
||||
return diffFileListInCommits;
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getDiffFileListInBranches] [error]");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mergeBranch(String workspaceId, String srcBranch, String tgtBranch, String message) throws RuntimeException {
|
||||
log.info("[mergeBranch] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch);
|
||||
@@ -458,15 +660,8 @@ public class GitServiceImpl implements IGitService {
|
||||
.put("path", treeWalk.getPathString())
|
||||
.build();
|
||||
|
||||
ObjectLoader loader = repository.open(treeWalk.getObjectId(0));
|
||||
if (this.isBinary(treeWalk.getNameString())) {
|
||||
String content = Base64.getEncoder().encodeToString(loader.getBytes());
|
||||
m.put("encoding", "base64");
|
||||
m.put("content", content);
|
||||
} else {
|
||||
m.put("encoding", "");
|
||||
m.put("content", T.StrUtil.utf8Str(loader.getBytes()));
|
||||
}
|
||||
Map<Object, Object> fileContent = this.getFileContent(repository, treeWalk.getPathString(), treeWalk.getObjectId(0));
|
||||
m.putAll(fileContent);
|
||||
|
||||
// lastCommitId
|
||||
Iterable<RevCommit> iterable = git.log()
|
||||
@@ -551,7 +746,7 @@ public class GitServiceImpl implements IGitService {
|
||||
String message = String.format("feat: add %s application", applicationName);
|
||||
this.createCommit(repository, branch, newTreeId, message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | ConcurrentRefUpdateException e) {
|
||||
log.error(e, "[newApplication] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName);
|
||||
throw new ASWException(RCode.ERROR);
|
||||
}
|
||||
@@ -590,7 +785,7 @@ public class GitServiceImpl implements IGitService {
|
||||
String message = String.format("refactor: remove %s application", applicationName);
|
||||
this.createCommit(repository, branch, newTreeId, message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | ConcurrentRefUpdateException e) {
|
||||
log.error(e, "[deleteApplication] [error] [workspaceId: {}] [branch: {}] [application: {}]", workspaceId, branch, applicationName);
|
||||
throw new ASWException(RCode.ERROR);
|
||||
}
|
||||
@@ -729,7 +924,7 @@ public class GitServiceImpl implements IGitService {
|
||||
.put("path", path)
|
||||
.build();
|
||||
|
||||
try (Git git = getGitInstance(workspaceId)) {
|
||||
try (Git git = this.getGitInstance(workspaceId)) {
|
||||
Repository repository = git.getRepository();
|
||||
|
||||
try (TreeWalk treeWalk = new TreeWalk(repository);
|
||||
@@ -743,15 +938,8 @@ public class GitServiceImpl implements IGitService {
|
||||
|
||||
while (treeWalk.next()) {
|
||||
if (T.StrUtil.equals(path, treeWalk.getPathString())) {
|
||||
ObjectLoader loader = repository.open(treeWalk.getObjectId(0));
|
||||
if (this.isBinary(treeWalk.getNameString())) {
|
||||
String content = Base64.getEncoder().encodeToString(loader.getBytes());
|
||||
result.put("encoding", "base64");
|
||||
result.put("content", content);
|
||||
} else {
|
||||
result.put("encoding", "");
|
||||
result.put("content", T.StrUtil.utf8Str(loader.getBytes()));
|
||||
}
|
||||
Map<Object, Object> fileContent = this.getFileContent(repository, treeWalk.getPathString(), treeWalk.getObjectId(0));
|
||||
result.putAll(fileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,6 +950,419 @@ public class GitServiceImpl implements IGitService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 srcBranch tgtBranch 共同祖先之后, srcBranch 的提交记录
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param srcBranch
|
||||
* @param tgtBranch
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<Map<Object, Object>> listCommitAfterMergeBase(String workspaceId, String srcBranch, String tgtBranch) {
|
||||
log.info("[listCommitAfterMergeBase] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch);
|
||||
List<Map<Object, Object>> commitList = T.ListUtil.list(true);
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();) {
|
||||
ObjectId branch1Id = repository.resolve(srcBranch);
|
||||
ObjectId branch2Id = repository.resolve(tgtBranch);
|
||||
|
||||
// Find the merge base
|
||||
String mergeBaseId = this.getMergeBase(repository, branch1Id, branch2Id);
|
||||
log.info("[listCommitAfterMergeBase] [mergeBase: {}]", mergeBaseId);
|
||||
if (T.StrUtil.isEmpty(mergeBaseId)) {
|
||||
return commitList;
|
||||
}
|
||||
|
||||
// Walk branch1's commits since the merge base
|
||||
try (RevWalk revWalk = new RevWalk(repository)) {
|
||||
revWalk.sort(RevSort.TOPO);
|
||||
|
||||
RevCommit mergeBase = revWalk.parseCommit(ObjectId.fromString(mergeBaseId));
|
||||
revWalk.markStart(revWalk.parseCommit(branch1Id));
|
||||
revWalk.markUninteresting(mergeBase);
|
||||
|
||||
for (RevCommit commit : revWalk) {
|
||||
commitList.add(this.buildAswCommitInfo(commit));
|
||||
|
||||
List<String> parentIds = Arrays.stream(commit.getParents()).map(RevCommit::getName).collect(Collectors.toList());
|
||||
if (parentIds.contains(mergeBaseId)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[listCommitAfterMergeBase] [error]");
|
||||
throw new RuntimeException(e.getMessage());
|
||||
} finally {
|
||||
log.info("[listCommitAfterMergeBase] [finshed] [workspaceId: {}] [commits size: {}]", workspaceId, commitList.size());
|
||||
}
|
||||
return commitList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 srcBranch -> tgtBranch 冲突文件路径
|
||||
* srcBranch 合并到 tgtBranch
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param srcBranch
|
||||
* @param tgtBranch
|
||||
*/
|
||||
@Override
|
||||
public List<String> getConflictFilePathInBranches(String workspaceId, String srcBranch, String tgtBranch) {
|
||||
log.info("[getConflictFileListInBranches] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch);
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);) {
|
||||
|
||||
RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch));
|
||||
RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch));
|
||||
|
||||
// Find the merge base
|
||||
String mergeBaseId = this.getMergeBase(repository, commitA, commitB);
|
||||
log.info("[getConflictFileListInBranches] [mergeBase: {}]", mergeBaseId);
|
||||
|
||||
RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId));
|
||||
|
||||
ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
|
||||
threeWayMerger.setBase(mergeBase);
|
||||
|
||||
boolean isOk = threeWayMerger.merge(commitA, commitB);
|
||||
log.info("[getConflictFileListInBranches] [merge result] [isOk: {}]", isOk);
|
||||
if (!isOk) {
|
||||
List<String> unmergedPaths = ((RecursiveMerger) threeWayMerger).getUnmergedPaths();
|
||||
return unmergedPaths;
|
||||
}
|
||||
return T.ListUtil.empty();
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getConflictFileListInBranches] [error]");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 三路对比 srcBranch,tgtBranch,返回冲突文件详情
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param srcBranch
|
||||
* @param tgtBranch
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<Map<Object, Object>> getConflictFileInfoInBranches(String workspaceId, String srcBranch, String tgtBranch) {
|
||||
log.info("[getConflictFileInfoInBranches] [begin] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}]", workspaceId, srcBranch, tgtBranch);
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
RevWalk revWalk = new RevWalk(repository);) {
|
||||
|
||||
RevCommit commitA = revWalk.parseCommit(repository.resolve(srcBranch));
|
||||
RevCommit commitB = revWalk.parseCommit(repository.resolve(tgtBranch));
|
||||
|
||||
// Find the merge base
|
||||
String mergeBaseId = this.getMergeBase(repository, commitA, commitB);
|
||||
log.info("[getConflictFileInfoInBranches] [mergeBase: {}]", mergeBaseId);
|
||||
|
||||
RevCommit mergeBase = revWalk.parseCommit(repository.resolve(mergeBaseId));
|
||||
|
||||
ThreeWayMerger threeWayMerger = (ThreeWayMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
|
||||
threeWayMerger.setBase(mergeBase);
|
||||
|
||||
boolean isOk = threeWayMerger.merge(commitA, commitB);
|
||||
log.info("[getConflictFileInfoInBranches] [merge result] [isOk: {}]", isOk);
|
||||
|
||||
if (!isOk) {
|
||||
List<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 (this.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();
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[getConflictFileInfoInBranches] [error]");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 file object 添加到 objectDatabases,也就是 ./objects 目录下
|
||||
*
|
||||
* @param repository
|
||||
* @param data
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private ObjectId insertBlobFileToDatabase(Repository repository, byte[] data) throws IOException {
|
||||
try (ObjectInserter inserter = repository.getObjectDatabase().newInserter()) {
|
||||
ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, data);
|
||||
inserter.flush();
|
||||
return blobId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 path,objectId 读取文件内容
|
||||
* 响应 Map,key=encoding,content
|
||||
*
|
||||
* @param repository
|
||||
* @param path
|
||||
* @param objectId
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Map<Object, Object> getFileContent(Repository repository, String path, ObjectId objectId) throws IOException {
|
||||
String encoding = null, content = null;
|
||||
|
||||
ObjectLoader loader = repository.open(objectId);
|
||||
if (this.isBinary(T.FileNameUtil.getName(path))) {
|
||||
encoding = "base64";
|
||||
content = Base64.getEncoder().encodeToString(loader.getBytes());
|
||||
} else {
|
||||
content = T.StrUtil.utf8Str(loader.getBytes());
|
||||
}
|
||||
return T.MapUtil.builder()
|
||||
.put("encoding", encoding)
|
||||
.put("content", content)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the merge base of two commits or null if there is no common ancestry.
|
||||
* 返回两个提交的合并基础,如果没有共同祖先,则返回 null。
|
||||
*
|
||||
* @param repository
|
||||
* @param commitIdA
|
||||
* @param commitIdB
|
||||
* @return the commit id of the merge base or null if there is no common base
|
||||
*/
|
||||
public String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
|
||||
try (RevWalk rw = new RevWalk(repository)) {
|
||||
RevCommit a = rw.lookupCommit(commitIdA);
|
||||
RevCommit b = rw.lookupCommit(commitIdB);
|
||||
|
||||
rw.setRevFilter(RevFilter.MERGE_BASE);
|
||||
rw.markStart(a);
|
||||
rw.markStart(b);
|
||||
RevCommit mergeBase = rw.next();
|
||||
if (mergeBase == null) {
|
||||
return null;
|
||||
}
|
||||
return mergeBase.getName();
|
||||
} catch (Exception e) {
|
||||
log.error(e, "Failed to determine merge base");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决冲突提交到 srcBranch
|
||||
* 提交指定 srcBranch,tgtBranch 最新 commitId,像 merge commit 一样
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param srcBranch
|
||||
* @param tgtBranch
|
||||
* @param commitId
|
||||
* @param commitMessage
|
||||
* @param files
|
||||
*/
|
||||
@Override
|
||||
public void resolveMrConflicts(String workspaceId, String srcBranch, String tgtBranch, String commitId, String commitMessage, List<Map<String, String>> files) {
|
||||
try (Git git = this.getGitInstance(workspaceId);
|
||||
Repository repository = git.getRepository();
|
||||
TreeWalk treeWalk = new TreeWalk(repository);
|
||||
RevWalk revWalk = new RevWalk(repository)) {
|
||||
|
||||
ObjectId srcBranchLatestCommitId = repository.resolve(srcBranch);
|
||||
|
||||
// branch tree
|
||||
treeWalk.addTree(revWalk.parseTree(srcBranchLatestCommitId));
|
||||
treeWalk.setRecursive(true);
|
||||
|
||||
DirCache newTree = DirCache.newInCore();
|
||||
DirCacheBuilder newTreeBuilder = newTree.builder();
|
||||
|
||||
List<String> updateFilePath = files.stream().map(map -> T.MapUtil.getStr(map, "path")).collect(Collectors.toList());
|
||||
while (treeWalk.next()) {
|
||||
String pathString = treeWalk.getPathString();
|
||||
// 先删
|
||||
if (!updateFilePath.contains(pathString)) {
|
||||
DirCacheEntry entry = new DirCacheEntry(pathString);
|
||||
entry.setFileMode(treeWalk.getFileMode(0));
|
||||
entry.setObjectId(treeWalk.getObjectId(0));
|
||||
newTreeBuilder.add(entry);
|
||||
}
|
||||
}
|
||||
// 后增
|
||||
for (Map<String, String> map : files) {
|
||||
String path = T.MapUtil.getStr(map, "path");
|
||||
DirCacheEntry dirCacheEntry = new DirCacheEntry(path);
|
||||
dirCacheEntry.setFileMode(FileMode.REGULAR_FILE);
|
||||
|
||||
String content = T.MapUtil.getStr(map, "content");
|
||||
ObjectId objectId = this.insertBlobFileToDatabase(repository, content.getBytes());
|
||||
dirCacheEntry.setObjectId(objectId);
|
||||
newTreeBuilder.add(dirCacheEntry);
|
||||
}
|
||||
newTreeBuilder.finish();
|
||||
|
||||
// merge commit
|
||||
ObjectId parentId1 = ObjectId.fromString(commitId);
|
||||
RevCommit revCommit = revWalk.parseCommit(repository.resolve(tgtBranch));
|
||||
ObjectId parentId2 = revCommit.getId();
|
||||
|
||||
boolean success = this.commitIndex(repository, srcBranch, newTree, parentId1, parentId2, commitMessage);
|
||||
log.info("[resolveMrConflicts] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}] [commitId: {}] [success: {}]", workspaceId, srcBranch, tgtBranch, commitId, success);
|
||||
} catch (IOException | ConcurrentRefUpdateException e) {
|
||||
log.error(e, "[resolveMrConflicts] [error] [workspaceId: {}] [srcBranch: {}] [tgtBranch: {}] [commitId: {}]", workspaceId, srcBranch, tgtBranch, commitId);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* commit
|
||||
*
|
||||
* @param repo
|
||||
* @param branch
|
||||
* @param treeId
|
||||
* @param message
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createCommit(Repository repo, String branch, ObjectId treeId, String message) throws IOException, ConcurrentRefUpdateException {
|
||||
try (ObjectInserter inserter = repo.getObjectDatabase().newInserter();
|
||||
RevWalk revWalk = new RevWalk(repo);) {
|
||||
|
||||
CommitBuilder builder = new CommitBuilder();
|
||||
builder.setTreeId(treeId);
|
||||
builder.setMessage(message);
|
||||
|
||||
ObjectId branchRef = repo.resolve(branch);
|
||||
if (null != branchRef) {
|
||||
RevCommit parentId = revWalk.parseCommit(branchRef);
|
||||
builder.setParentId(parentId);
|
||||
}
|
||||
|
||||
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
|
||||
builder.setAuthor(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com"));
|
||||
builder.setCommitter(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com"));
|
||||
|
||||
// 插入新的提交对象
|
||||
ObjectId commitId = inserter.insert(builder);
|
||||
inserter.flush();
|
||||
|
||||
// 更新 branch 指向新的提交
|
||||
RefUpdate ru = null;
|
||||
RefUpdate.Result rc = null;
|
||||
if (null != branchRef) {
|
||||
ru = repo.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch));
|
||||
ru.setNewObjectId(commitId);
|
||||
rc = ru.update();
|
||||
} else {
|
||||
ru = repo.updateRef(Constants.HEAD);
|
||||
ru.setNewObjectId(commitId);
|
||||
rc = ru.update();
|
||||
}
|
||||
switch (rc) {
|
||||
case NEW:
|
||||
case FORCED:
|
||||
case FAST_FORWARD:
|
||||
break;
|
||||
case REJECTED:
|
||||
case LOCK_FAILURE:
|
||||
throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
|
||||
default:
|
||||
throw new JGitInternalException(MessageFormat.format(
|
||||
JGitText.get().updatingRefFailed, branch, commitId.toString(),
|
||||
rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* commit index
|
||||
*
|
||||
* @param repo
|
||||
* @param branch
|
||||
* @param index
|
||||
* @param parentId1
|
||||
* @param parentId2
|
||||
* @param message
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ConcurrentRefUpdateException
|
||||
*/
|
||||
public boolean commitIndex(Repository repo, String branch, DirCache index, ObjectId parentId1, ObjectId parentId2, String message) throws IOException, ConcurrentRefUpdateException {
|
||||
boolean success = false;
|
||||
try (ObjectInserter odi = repo.newObjectInserter()) {
|
||||
|
||||
// new index
|
||||
ObjectId indexTreeId = index.writeTree(odi);
|
||||
|
||||
// PersonIdent
|
||||
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
|
||||
PersonIdent ident = new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com");
|
||||
|
||||
CommitBuilder commit = new CommitBuilder();
|
||||
commit.setAuthor(ident);
|
||||
commit.setCommitter(ident);
|
||||
|
||||
if (null != parentId1 && null == parentId2) {
|
||||
commit.setParentId(parentId1);
|
||||
}
|
||||
if (null != parentId1 && null != parentId2) {
|
||||
commit.setParentIds(parentId1, parentId2);
|
||||
}
|
||||
|
||||
commit.setTreeId(indexTreeId);
|
||||
commit.setMessage(message);
|
||||
|
||||
|
||||
// insert the commit into the repository
|
||||
ObjectId commitId = odi.insert(commit);
|
||||
odi.flush();
|
||||
|
||||
RefUpdate ru = repo.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch));
|
||||
ru.setNewObjectId(commitId);
|
||||
RefUpdate.Result rc = ru.update();
|
||||
switch (rc) {
|
||||
case NEW:
|
||||
case FORCED:
|
||||
case FAST_FORWARD:
|
||||
success = true;
|
||||
break;
|
||||
case REJECTED:
|
||||
case LOCK_FAILURE:
|
||||
throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
|
||||
default:
|
||||
throw new JGitInternalException(MessageFormat.format(
|
||||
JGitText.get().updatingRefFailed, branch, commitId.toString(),
|
||||
rc));
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* build asw commit info
|
||||
*
|
||||
@@ -796,64 +1397,4 @@ public class GitServiceImpl implements IGitService {
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 file object 添加到 objectDatabases,也就是 ./objects 目录下
|
||||
*
|
||||
* @param repository
|
||||
* @param data
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private ObjectId insertBlobFileToDatabase(Repository repository, byte[] data) throws IOException {
|
||||
try (ObjectInserter inserter = repository.getObjectDatabase().newInserter()) {
|
||||
ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, data);
|
||||
inserter.flush();
|
||||
return blobId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* commit
|
||||
*
|
||||
* @param repo
|
||||
* @param branch
|
||||
* @param treeId
|
||||
* @param message
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createCommit(Repository repo, String branch, ObjectId treeId, String message) throws IOException {
|
||||
try (ObjectInserter inserter = repo.getObjectDatabase().newInserter();
|
||||
RevWalk revWalk = new RevWalk(repo);) {
|
||||
|
||||
CommitBuilder builder = new CommitBuilder();
|
||||
builder.setTreeId(treeId);
|
||||
builder.setMessage(message);
|
||||
|
||||
ObjectId branchRef = repo.resolve(branch);
|
||||
if (null != branchRef) {
|
||||
RevCommit parentId = revWalk.parseCommit(branchRef);
|
||||
builder.setParentId(parentId);
|
||||
}
|
||||
|
||||
SysUserEntity loginUserEntity = userService.getById(StpUtil.getLoginIdAsString());
|
||||
builder.setAuthor(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com"));
|
||||
builder.setCommitter(new PersonIdent(loginUserEntity.getName(), "asw@geedgenetworks.com"));
|
||||
|
||||
// 插入新的提交对象
|
||||
ObjectId commitId = inserter.insert(builder);
|
||||
inserter.flush();
|
||||
|
||||
// 更新 branch 指向新的提交
|
||||
if (null != branchRef) {
|
||||
RefUpdate refUpdate = repo.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch));
|
||||
refUpdate.setNewObjectId(commitId);
|
||||
refUpdate.update();
|
||||
} else {
|
||||
RefUpdate refUpdate = repo.updateRef(Constants.HEAD);
|
||||
refUpdate.setNewObjectId(commitId);
|
||||
refUpdate.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -149,5 +149,9 @@ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (241, '302003', 'PLAYBOOK_INVALID_FILE', '无效文件', 'zh', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (242, '202004', 'PACKAGE_CANNOT_DELETE', 'The referenced package cannot be deleted', 'en', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (243, '202004', 'PACKAGE_CANNOT_DELETE', '以引用安装包不能删除', 'zh', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (244, '203003', 'GIT_PARENT_COMMITID_NOT_FOUND', 'Parent commitId not found', 'en', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (245, '203003', 'GIT_PARENT_COMMITID_NOT_FOUND', '未找到 parent commitId', 'zh', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (246, '203004', 'GIT_BINARY_CONFLICT_ERROR', 'Binary file conflict found; resolve conflicts in binary files manually', 'en', '', 'admin', 1724030366000);
|
||||
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (247, '203004', 'GIT_BINARY_CONFLICT_ERROR', '发现二进制文件冲突;手动解决二进制文件中的冲突', 'zh', '', 'admin', 1724030366000);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
Reference in New Issue
Block a user