需求
生成一系列结构相同的项目代码,将这些项目的代码推送至一个指定的 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();// 生成 READMEFile 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