需求
生成一系列结构相同的项目代码,将这些项目的代码推送至一个指定的 Git
仓库,每个项目独占一个分支。
推送时若仓库不存在,则自动创建仓库。
分析
生成代码使用 Java 程序模拟,每个项目中模拟三个文件。Project.cpp
、Project.h
和 README
。
使用 JGit
实现代码版本管理与推送。
代码实现
以下代码包含了代码生成,Git 仓库初始化、代码克隆、分支检出、代码修改、暂存、提交及推送等操作。
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import cn.hutool.core.io.FileUtil;
public class JgitTest3 {
private static String DEFAULE_REMOTE_NAME = "origin";
private static String DEFAULE_BRANCH_NAME = "master";
private static String gitUrl = "http://192.168.181.1:3000/root/a-test.git";
private static String username = "root";
private static String password = "123456";
/**
* 创建认证信息
*
* @return
*/
public static CredentialsProvider getCredentialsProvider() {
return new UsernamePasswordCredentialsProvider(username, password);
}
public static void main(String[] args) throws Exception {
List<String> branches = Stream.of("branchA", "branchB").collect(Collectors.toList());
for (String branchName : branches) {
System.out.println("\r\n ==========================<<" + branchName + ">>==========================\r\n");
// 生成代码
File codeDir = genCode(branchName);
System.out.println(" >>>>>>>>>>>>>>> CodeDir: " + codeDir);
// 判断仓库是否存在
boolean remoteRepositoryExist = remoteRepositoryExist(gitUrl, username, password);
if (remoteRepositoryExist) {
pushOnRepoExist(branchName, codeDir);
} else {
pushOnRepoNotExist(branchName, codeDir);
}
// 清理生成的文件
FileUtil.del(codeDir);
}
}
private static void pushOnRepoNotExist(String branchName, File codeDir) throws Exception {
// 将源码库初始化为Git仓库
Git git = Git.init().setDirectory(codeDir).call();
// 添加远程仓库
git.remoteAdd()
.setName(DEFAULE_REMOTE_NAME)
.setUri(new URIish(gitUrl))
.call();
// 初始化提交
git.add().addFilepattern(".").call();
git.commit().setMessage("Initial commit").call();
// 切换分支
checkoutBranch(git, branchName);
// 提交推送
pushToRepo(git, branchName);
// 关闭资源
git.close();
}
private static void pushOnRepoExist(String branchName, File codeDir) throws Exception {
// 创建临时工作目录
File localDir = Files.createTempDirectory("Jgit-work-dir-").toFile();
System.out.println("\r\n >>>>>>>>>>>>>>> Work dir is: " + localDir + "\r\n");
// 克隆到本地
Git git = Git.cloneRepository()
.setDirectory(localDir)
.setURI(gitUrl)
.setCredentialsProvider(getCredentialsProvider())
.call();
// 切换到分支
checkoutBranch(git, branchName);
// 更新代码
deleteContent(localDir.toPath(), ".git");
// git.add().addFilepattern(".").setUpdate(true).call();
// git.commit().setMessage("rm origin files").call();
FileUtil.copyContent(codeDir, localDir, true);
// 提交、推送
pushToRepo(git, branchName);
// 关闭资源
git.close();
FileUtil.del(localDir);
}
/**
* 推送到远端分支
*
* @param git
* @param branchName 远端分支
* @throws Exception
*/
private static Iterable<PushResult> pushToRepo(Git git, String branchName) throws Exception {
// 加入暂存区
git.add().addFilepattern(".").call();
git.add().addFilepattern(".").setUpdate(true).call();
// 提交
git.commit().setMessage(" Commit at : " + LocalDateTime.now()).call();
// 构建推送命令
PushCommand pushCmd = git.push()
.setRemote(DEFAULE_REMOTE_NAME)
.setCredentialsProvider(getCredentialsProvider());
Ref remoteBranchRef = git.getRepository().findRef("refs/remotes/" + DEFAULE_REMOTE_NAME + "/" + branchName);
if (Objects.isNull(remoteBranchRef)) {
pushCmd.add(branchName);
} else {
pushCmd.setForce(true);
}
// 推送
return pushCmd.call();
}
/**
* 切换分支
* <p>
* <ul>
* <li>先判断本地分支是否存在,存在则直接切换,不存在则下一步。</li>
* <li>判断远程分支是否存在,存在则直接切换,不存在则在切换时创建分支。</li>
* </ul>
*
* </p>
*
* @param git
* @param branchName
*/
private static void checkoutBranch(Git git, String branchName) throws Exception {
CheckoutCommand checkoutCmd = git.checkout().setName(branchName);
Repository repository = git.getRepository();
Ref branchRef = repository.findRef("refs/heads/" + branchName);
if (Objects.isNull(branchRef)) {
Ref remoteBranchRef = repository.findRef("refs/remotes/" + DEFAULE_REMOTE_NAME + "/" + branchName);
if (Objects.isNull(remoteBranchRef)) {
CreateBranchCommand createBranchCmd = git.branchCreate().setName(branchName);
// 先切换到已有分支,以获取提交记录,设置提交点辅助创建新的分支
List<Ref> branches = git.branchList()
.setListMode(ListBranchCommand.ListMode.REMOTE)
.call();
if (Objects.nonNull(branches) && branches.size() > 0) {
Ref remoteRef = branches.get(0);
String bName = Repository.shortenRefName(remoteRef.getName());
git.checkout().setName(bName).call();
RevCommit latestCommit = git.log().setMaxCount(1).call().iterator().next();
if (Objects.nonNull(latestCommit)) {
createBranchCmd.setStartPoint(latestCommit);
}
}
createBranchCmd.call();
} else {
checkoutCmd.setCreateBranch(true);
}
}
checkoutCmd.call();
}
/**
* 删除目录中的所有文件
* <ul>
* <li>文件</li>
* <li>文件夹</li>
* <li>子文件</li>
* <li>子文件夹</li>
* </ul>
*
* @param folderPath 目标文件夹
* @param ignoreFiles 忽略的文件或文件夹
*/
public static void deleteContent(Path folderPath, String... ignoreFiles) {
try {
Files.walk(folderPath)
.filter(path -> !path.equals(folderPath))
.filter(path -> {
if (Objects.isNull(ignoreFiles) || ignoreFiles.length == 0) {
return false;
}
for (String ig : ignoreFiles) {
if (StringUtils.contains(path.toAbsolutePath().toString(), ig)) {
return false;
}
}
return true;
})
.forEach(path -> {
try {
if (path.toFile().isDirectory()) {
deleteContent(path, ignoreFiles);
} else {
Files.deleteIfExists(path);
}
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成代码
*
* @param branchName
* @return
* @throws Exception
*/
private static File genCode(String branchName) throws Exception {
File codeDir = Files.createTempDirectory("Jgit-source-dir-").toFile();
String codeDirAbsPath = codeDir.getAbsolutePath();
// 生成 README
File addNewFile = new File((codeDirAbsPath.concat(File.separator).concat("README.md")));
String readmeContent = "Project for " + branchName + "\r\nWrite By Code JGitTest ";
Files.write(addNewFile.toPath(), readmeContent.getBytes());
// 生成文件
File cppFile = new File(codeDirAbsPath.concat(File.separator).concat(branchName.concat(".cpp")));
String cppContent = "Cpp Code for " + branchName + "\r\n Write By Code JGitTest ";
Files.write(cppFile.toPath(), cppContent.getBytes());
// 生成文件
File hFile = new File(codeDirAbsPath.concat(File.separator).concat(branchName.concat(".h")));
String hContent = "Header code for " + branchName + "\r\nWrite By Code JGitTest ";
Files.write(hFile.toPath(), hContent.getBytes());
return codeDir;
}
/**
* 判断远程仓库是不是存在
*
* @param remoteUrl 远程仓库地址
* @return true(存在)/false(不存在)
*/
public static boolean remoteRepositoryExist(String remoteUrl, String username, String password) {
try {
Collection<Ref> refs = (Collection<Ref>) Git.lsRemoteRepository()
.setHeads(true)
.setTags(true)
.setCredentialsProvider(getCredentialsProvider(username, password))
.setRemote(remoteUrl)
.call();
if (refs.isEmpty()) {
return false;
} else {
return true;
}
} catch (Exception e) {
log.warn("仓库{}不存在", remoteUrl);
return false;
}
}
}
成果展示
附
1、JGit 版本
<!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>5.1.3.201810200350-r</version>
</dependency>
2、Gitea 安装
- Win 下 Docker 安装 Gitea 实践:windows docker desktop部署gitea
3、JGit 资料
- 【JGit】简述及学习资料整理
- 【Gitea】Java 使用 JGit 创建 Git 代码仓库
- 【Git】 删除远程分支
- 【Gitea】配置 Push To Create