chore: initial project setup
This commit is contained in:
48
src/main/java/com/svnmanager/MainApp.java
Normal file
48
src/main/java/com/svnmanager/MainApp.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.svnmanager;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SVN管理器主应用
|
||||
*/
|
||||
public class MainApp extends Application {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainApp.class);
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
try {
|
||||
logger.info("启动SVN管理器应用");
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"));
|
||||
Scene scene = new Scene(loader.load(), 1200, 800);
|
||||
|
||||
primaryStage.setTitle("SVN管理器 - 多项目管理");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setMinWidth(1000);
|
||||
primaryStage.setMinHeight(600);
|
||||
|
||||
// 设置窗口关闭事件
|
||||
primaryStage.setOnCloseRequest(e -> {
|
||||
logger.info("应用关闭");
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
primaryStage.show();
|
||||
logger.info("应用启动成功");
|
||||
} catch (IOException e) {
|
||||
logger.error("启动应用失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
559
src/main/java/com/svnmanager/controller/MainController.java
Normal file
559
src/main/java/com/svnmanager/controller/MainController.java
Normal file
@@ -0,0 +1,559 @@
|
||||
package com.svnmanager.controller;
|
||||
|
||||
import com.svnmanager.model.Project;
|
||||
import com.svnmanager.model.SvnFileStatus;
|
||||
import com.svnmanager.model.SvnStatus;
|
||||
import com.svnmanager.service.*;
|
||||
import com.svnmanager.util.ConfigUtil;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 主窗口控制器
|
||||
*/
|
||||
public class MainController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainController.class);
|
||||
|
||||
@FXML private VBox sidebar;
|
||||
@FXML private VBox projectList;
|
||||
@FXML private Label projectCountLabel;
|
||||
@FXML private Button addProjectButton;
|
||||
|
||||
@FXML private Label projectTitleLabel;
|
||||
@FXML private Label projectPathLabel;
|
||||
@FXML private Button refreshButton;
|
||||
@FXML private Button executeButton;
|
||||
|
||||
@FXML private Button checkoutButton;
|
||||
@FXML private Button updateButton;
|
||||
@FXML private Button commitButton;
|
||||
@FXML private Button statusButton;
|
||||
@FXML private Button logButton;
|
||||
@FXML private Button diffButton;
|
||||
@FXML private Button infoButton;
|
||||
|
||||
@FXML private Label currentVersionLabel;
|
||||
@FXML private Label workingCopyLabel;
|
||||
@FXML private Label modifiedFilesLabel;
|
||||
@FXML private TableView<SvnFileStatus> fileStatusTable;
|
||||
@FXML private TableColumn<SvnFileStatus, String> statusColumn;
|
||||
@FXML private TableColumn<SvnFileStatus, String> pathColumn;
|
||||
@FXML private TableColumn<SvnFileStatus, String> actionColumn;
|
||||
|
||||
private Project currentProject;
|
||||
private ObservableList<SvnFileStatus> fileStatusList;
|
||||
private ObservableList<Project> projects;
|
||||
|
||||
// 服务实例
|
||||
private CheckoutService checkoutService;
|
||||
private UpdateService updateService;
|
||||
private CommitService commitService;
|
||||
private StatusService statusService;
|
||||
private LogService logService;
|
||||
private DiffService diffService;
|
||||
private InfoService infoService;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
logger.info("初始化主控制器");
|
||||
|
||||
// 初始化服务
|
||||
checkoutService = new CheckoutService();
|
||||
updateService = new UpdateService();
|
||||
commitService = new CommitService();
|
||||
statusService = new StatusService();
|
||||
logService = new LogService();
|
||||
diffService = new DiffService();
|
||||
infoService = new InfoService();
|
||||
|
||||
// 初始化文件状态列表
|
||||
fileStatusList = FXCollections.observableArrayList();
|
||||
fileStatusTable.setItems(fileStatusList);
|
||||
|
||||
// 配置表格列
|
||||
statusColumn.setCellValueFactory(data -> {
|
||||
SvnFileStatus.FileStatus status = data.getValue().getStatus();
|
||||
return new javafx.beans.property.SimpleStringProperty(status.getDisplayName());
|
||||
});
|
||||
|
||||
pathColumn.setCellValueFactory(data ->
|
||||
new javafx.beans.property.SimpleStringProperty(data.getValue().getPath()));
|
||||
|
||||
actionColumn.setCellFactory(param -> new TableCell<SvnFileStatus, String>() {
|
||||
private final Button viewButton = new Button("查看");
|
||||
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
viewButton.setOnAction(e -> handleViewFile(getTableView().getItems().get(getIndex())));
|
||||
setGraphic(viewButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 加载项目列表
|
||||
loadProjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载项目列表
|
||||
*/
|
||||
private void loadProjects() {
|
||||
projects = FXCollections.observableArrayList(ConfigUtil.loadProjects());
|
||||
projectList.getChildren().clear();
|
||||
|
||||
for (Project project : projects) {
|
||||
VBox projectCard = createProjectCard(project);
|
||||
projectList.getChildren().add(projectCard);
|
||||
}
|
||||
|
||||
updateProjectCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目卡片
|
||||
*/
|
||||
private VBox createProjectCard(Project project) {
|
||||
VBox card = new VBox(8);
|
||||
card.getStyleClass().add("project-card");
|
||||
card.setPrefWidth(280);
|
||||
card.setPadding(new javafx.geometry.Insets(16));
|
||||
|
||||
HBox content = new HBox(12);
|
||||
|
||||
// 项目图标
|
||||
Circle icon = new Circle(24);
|
||||
icon.setFill(Color.web("#6366f1"));
|
||||
|
||||
VBox info = new VBox(4);
|
||||
Label nameLabel = new Label(project.getName());
|
||||
nameLabel.getStyleClass().add("project-name");
|
||||
Label pathLabel = new Label(project.getPath());
|
||||
pathLabel.getStyleClass().add("project-path");
|
||||
pathLabel.setWrapText(true);
|
||||
|
||||
// 状态徽章
|
||||
HBox statusBox = new HBox(4);
|
||||
Circle statusDot = new Circle(4);
|
||||
statusDot.getStyleClass().add("status-dot");
|
||||
Label statusLabel = new Label(project.getStatus().getDisplayName());
|
||||
statusBox.getChildren().addAll(statusDot, statusLabel);
|
||||
statusBox.getStyleClass().add("status-badge");
|
||||
|
||||
info.getChildren().addAll(nameLabel, pathLabel, statusBox);
|
||||
content.getChildren().addAll(icon, info);
|
||||
card.getChildren().add(content);
|
||||
|
||||
// 点击事件
|
||||
card.setOnMouseClicked(e -> selectProject(project));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择项目
|
||||
*/
|
||||
private void selectProject(Project project) {
|
||||
currentProject = project;
|
||||
projectTitleLabel.setText(project.getName());
|
||||
projectPathLabel.setText(project.getPath());
|
||||
|
||||
// 更新项目卡片样式
|
||||
for (int i = 0; i < projectList.getChildren().size(); i++) {
|
||||
VBox card = (VBox) projectList.getChildren().get(i);
|
||||
if (i == projects.indexOf(project)) {
|
||||
card.getStyleClass().add("active");
|
||||
} else {
|
||||
card.getStyleClass().remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新项目状态
|
||||
refreshProjectStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新项目状态
|
||||
*/
|
||||
private void refreshProjectStatus() {
|
||||
if (currentProject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Task<Void> task = new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
try {
|
||||
// 获取SVN信息
|
||||
com.svnmanager.model.SvnInfo info = infoService.getInfo(currentProject.getPath());
|
||||
if (info != null && info.getRevision() != null) {
|
||||
Platform.runLater(() -> {
|
||||
currentVersionLabel.setText("r" + info.getRevision());
|
||||
workingCopyLabel.setText("r" + info.getRevision());
|
||||
});
|
||||
}
|
||||
|
||||
// 获取状态
|
||||
SvnStatus status = statusService.getStatus(currentProject.getPath());
|
||||
Platform.runLater(() -> {
|
||||
fileStatusList.clear();
|
||||
fileStatusList.addAll(status.getFiles());
|
||||
modifiedFilesLabel.setText(String.valueOf(status.getTotalChangedFiles()));
|
||||
});
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 无效的工作副本
|
||||
logger.warn("无效的工作副本: {}", e.getMessage());
|
||||
Platform.runLater(() -> {
|
||||
currentVersionLabel.setText("r0");
|
||||
workingCopyLabel.setText("r0");
|
||||
modifiedFilesLabel.setText("0");
|
||||
fileStatusList.clear();
|
||||
showWarning("警告", "无效的SVN工作副本: " + e.getMessage());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("刷新项目状态失败", e);
|
||||
Platform.runLater(() -> {
|
||||
showError("刷新失败", e.getMessage());
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目计数
|
||||
*/
|
||||
private void updateProjectCount() {
|
||||
projectCountLabel.setText("共 " + projects.size() + " 个项目");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleAddProject() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/project-dialog.fxml"));
|
||||
DialogPane dialogPane = loader.load();
|
||||
ProjectDialogController controller = loader.getController();
|
||||
|
||||
Dialog<ButtonType> dialog = new Dialog<>();
|
||||
dialog.setDialogPane(dialogPane);
|
||||
dialog.setTitle("添加项目");
|
||||
|
||||
dialog.showAndWait().ifPresent(result -> {
|
||||
if (result == ButtonType.OK) {
|
||||
Project project = controller.getProject();
|
||||
if (project != null) {
|
||||
ConfigUtil.addProject(project);
|
||||
loadProjects();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
logger.error("打开添加项目对话框失败", e);
|
||||
showError("错误", "无法打开添加项目对话框");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleRefresh() {
|
||||
refreshProjectStatus();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleExecute() {
|
||||
// 执行操作按钮的功能可以根据需要实现
|
||||
showInfo("提示", "请选择具体的操作");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleCheckout() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
// 实现Checkout功能
|
||||
showInfo("提示", "Checkout功能待实现");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleUpdate() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<UpdateService.UpdateResult> task = new Task<UpdateService.UpdateResult>() {
|
||||
@Override
|
||||
protected UpdateService.UpdateResult call() throws Exception {
|
||||
return updateService.update(currentProject.getPath());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
UpdateService.UpdateResult result = task.getValue();
|
||||
if (result.isSuccess()) {
|
||||
showInfo("成功", "更新成功,版本: " + result.getRevision());
|
||||
refreshProjectStatus();
|
||||
} else {
|
||||
showError("失败", result.getError());
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleCommit() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("提交修改");
|
||||
dialog.setHeaderText("请输入提交消息");
|
||||
dialog.setContentText("消息:");
|
||||
|
||||
dialog.showAndWait().ifPresent(message -> {
|
||||
if (message.trim().isEmpty()) {
|
||||
showWarning("警告", "提交消息不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<CommitService.CommitResult> task = new Task<CommitService.CommitResult>() {
|
||||
@Override
|
||||
protected CommitService.CommitResult call() throws Exception {
|
||||
return commitService.commit(currentProject.getPath(), message);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
CommitService.CommitResult result = task.getValue();
|
||||
if (result.isSuccess()) {
|
||||
showInfo("成功", "提交成功,版本: " + result.getRevision());
|
||||
refreshProjectStatus();
|
||||
} else {
|
||||
showError("失败", result.getError());
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleStatus() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<SvnStatus> task = new Task<SvnStatus>() {
|
||||
@Override
|
||||
protected SvnStatus call() throws Exception {
|
||||
return statusService.getStatus(currentProject.getPath());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
SvnStatus status = task.getValue();
|
||||
fileStatusList.clear();
|
||||
fileStatusList.addAll(status.getFiles());
|
||||
modifiedFilesLabel.setText(String.valueOf(status.getTotalChangedFiles()));
|
||||
showInfo("状态", "共 " + status.getTotalChangedFiles() + " 个文件有变更");
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleLog() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<List<com.svnmanager.model.SvnLog>> task = new Task<List<com.svnmanager.model.SvnLog>>() {
|
||||
@Override
|
||||
protected List<com.svnmanager.model.SvnLog> call() throws Exception {
|
||||
return logService.getLog(currentProject.getPath(), 20);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
List<com.svnmanager.model.SvnLog> logs = task.getValue();
|
||||
showLogDialog(logs);
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleDiff() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<String> task = new Task<String>() {
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
return diffService.getDiff(currentProject.getPath());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
String diff = task.getValue();
|
||||
showDiffDialog(diff);
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleInfo() {
|
||||
if (currentProject == null) {
|
||||
showWarning("警告", "请先选择项目");
|
||||
return;
|
||||
}
|
||||
|
||||
Task<com.svnmanager.model.SvnInfo> task = new Task<com.svnmanager.model.SvnInfo>() {
|
||||
@Override
|
||||
protected com.svnmanager.model.SvnInfo call() throws Exception {
|
||||
return infoService.getInfo(currentProject.getPath());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
com.svnmanager.model.SvnInfo info = task.getValue();
|
||||
showInfoDialog(info);
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("错误", task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private void handleViewFile(SvnFileStatus fileStatus) {
|
||||
// 实现查看文件功能
|
||||
showInfo("文件", fileStatus.getPath());
|
||||
}
|
||||
|
||||
private void showLogDialog(List<com.svnmanager.model.SvnLog> logs) {
|
||||
Dialog<Void> dialog = new Dialog<>();
|
||||
dialog.setTitle("SVN日志");
|
||||
|
||||
TextArea textArea = new TextArea();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (com.svnmanager.model.SvnLog log : logs) {
|
||||
sb.append("r").append(log.getRevision()).append(" | ")
|
||||
.append(log.getAuthor()).append(" | ")
|
||||
.append(log.getDate()).append("\n")
|
||||
.append(log.getMessage()).append("\n\n");
|
||||
}
|
||||
textArea.setText(sb.toString());
|
||||
textArea.setEditable(false);
|
||||
|
||||
dialog.getDialogPane().setContent(textArea);
|
||||
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
private void showDiffDialog(String diff) {
|
||||
Dialog<Void> dialog = new Dialog<>();
|
||||
dialog.setTitle("差异对比");
|
||||
|
||||
TextArea textArea = new TextArea(diff);
|
||||
textArea.setEditable(false);
|
||||
textArea.setPrefSize(800, 600);
|
||||
|
||||
dialog.getDialogPane().setContent(textArea);
|
||||
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
private void showInfoDialog(com.svnmanager.model.SvnInfo info) {
|
||||
Dialog<Void> dialog = new Dialog<>();
|
||||
dialog.setTitle("SVN信息");
|
||||
|
||||
TextArea textArea = new TextArea();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("路径: ").append(info.getPath()).append("\n");
|
||||
sb.append("URL: ").append(info.getUrl()).append("\n");
|
||||
sb.append("版本: ").append(info.getRevision()).append("\n");
|
||||
sb.append("仓库根: ").append(info.getRepositoryRoot()).append("\n");
|
||||
sb.append("最后修改作者: ").append(info.getLastChangedAuthor()).append("\n");
|
||||
sb.append("最后修改版本: ").append(info.getLastChangedRev()).append("\n");
|
||||
sb.append("最后修改日期: ").append(info.getLastChangedDate()).append("\n");
|
||||
textArea.setText(sb.toString());
|
||||
textArea.setEditable(false);
|
||||
|
||||
dialog.getDialogPane().setContent(textArea);
|
||||
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showWarning(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle(title);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showInfo(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.svnmanager.controller;
|
||||
|
||||
import com.svnmanager.model.Project;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 项目对话框控制器
|
||||
*/
|
||||
public class ProjectDialogController {
|
||||
|
||||
@FXML private TextField nameField;
|
||||
@FXML private TextField pathField;
|
||||
@FXML private TextField svnUrlField;
|
||||
|
||||
private Project project;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// 初始化
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览路径
|
||||
*/
|
||||
@FXML
|
||||
private void handleBrowsePath() {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
directoryChooser.setTitle("选择项目目录");
|
||||
|
||||
File selectedDirectory = directoryChooser.showDialog(pathField.getScene().getWindow());
|
||||
if (selectedDirectory != null) {
|
||||
pathField.setText(selectedDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目
|
||||
*
|
||||
* @return 项目对象
|
||||
*/
|
||||
public Project getProject() {
|
||||
String name = nameField.getText().trim();
|
||||
String path = pathField.getText().trim();
|
||||
String svnUrl = svnUrlField.getText().trim();
|
||||
|
||||
if (name.isEmpty() || path.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (project == null) {
|
||||
project = new Project();
|
||||
}
|
||||
|
||||
project.setName(name);
|
||||
project.setPath(path);
|
||||
project.setSvnUrl(svnUrl);
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置项目(用于编辑)
|
||||
*
|
||||
* @param project 项目对象
|
||||
*/
|
||||
public void setProject(Project project) {
|
||||
this.project = project;
|
||||
if (project != null) {
|
||||
nameField.setText(project.getName());
|
||||
pathField.setText(project.getPath());
|
||||
svnUrlField.setText(project.getSvnUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/main/java/com/svnmanager/model/Project.java
Normal file
117
src/main/java/com/svnmanager/model/Project.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package com.svnmanager.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 项目数据模型
|
||||
*/
|
||||
public class Project {
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("path")
|
||||
private String path;
|
||||
|
||||
@JsonProperty("svnUrl")
|
||||
private String svnUrl;
|
||||
|
||||
@JsonProperty("currentVersion")
|
||||
private String currentVersion;
|
||||
|
||||
@JsonProperty("workingCopyVersion")
|
||||
private String workingCopyVersion;
|
||||
|
||||
@JsonProperty("status")
|
||||
private ProjectStatus status;
|
||||
|
||||
public Project() {
|
||||
this.status = ProjectStatus.UNKNOWN;
|
||||
}
|
||||
|
||||
public Project(String id, String name, String path, String svnUrl) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.svnUrl = svnUrl;
|
||||
this.status = ProjectStatus.UNKNOWN;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getSvnUrl() {
|
||||
return svnUrl;
|
||||
}
|
||||
|
||||
public void setSvnUrl(String svnUrl) {
|
||||
this.svnUrl = svnUrl;
|
||||
}
|
||||
|
||||
public String getCurrentVersion() {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public void setCurrentVersion(String currentVersion) {
|
||||
this.currentVersion = currentVersion;
|
||||
}
|
||||
|
||||
public String getWorkingCopyVersion() {
|
||||
return workingCopyVersion;
|
||||
}
|
||||
|
||||
public void setWorkingCopyVersion(String workingCopyVersion) {
|
||||
this.workingCopyVersion = workingCopyVersion;
|
||||
}
|
||||
|
||||
public ProjectStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(ProjectStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目状态枚举
|
||||
*/
|
||||
public enum ProjectStatus {
|
||||
SYNCED("已同步"),
|
||||
UPDATES_AVAILABLE("有更新"),
|
||||
DISCONNECTED("未连接"),
|
||||
UNKNOWN("未知");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
ProjectStatus(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/main/java/com/svnmanager/model/SvnFileStatus.java
Normal file
91
src/main/java/com/svnmanager/model/SvnFileStatus.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.svnmanager.model;
|
||||
|
||||
/**
|
||||
* SVN文件状态模型
|
||||
*/
|
||||
public class SvnFileStatus {
|
||||
private String path;
|
||||
private FileStatus status;
|
||||
private String workingCopyStatus;
|
||||
private String repositoryStatus;
|
||||
|
||||
public SvnFileStatus() {
|
||||
}
|
||||
|
||||
public SvnFileStatus(String path, FileStatus status) {
|
||||
this.path = path;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public FileStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(FileStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getWorkingCopyStatus() {
|
||||
return workingCopyStatus;
|
||||
}
|
||||
|
||||
public void setWorkingCopyStatus(String workingCopyStatus) {
|
||||
this.workingCopyStatus = workingCopyStatus;
|
||||
}
|
||||
|
||||
public String getRepositoryStatus() {
|
||||
return repositoryStatus;
|
||||
}
|
||||
|
||||
public void setRepositoryStatus(String repositoryStatus) {
|
||||
this.repositoryStatus = repositoryStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件状态枚举
|
||||
*/
|
||||
public enum FileStatus {
|
||||
MODIFIED('M', "已修改"),
|
||||
ADDED('A', "已添加"),
|
||||
DELETED('D', "已删除"),
|
||||
CONFLICTED('C', "冲突"),
|
||||
UNVERSIONED('?', "未版本控制"),
|
||||
MISSING('!', "缺失"),
|
||||
EXTERNAL('X', "外部"),
|
||||
IGNORED('I', "已忽略"),
|
||||
NORMAL(' ', "正常");
|
||||
|
||||
private final char code;
|
||||
private final String displayName;
|
||||
|
||||
FileStatus(char code, String displayName) {
|
||||
this.code = code;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public char getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public static FileStatus fromCode(char code) {
|
||||
for (FileStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/main/java/com/svnmanager/model/SvnInfo.java
Normal file
100
src/main/java/com/svnmanager/model/SvnInfo.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.svnmanager.model;
|
||||
|
||||
/**
|
||||
* SVN信息模型
|
||||
*/
|
||||
public class SvnInfo {
|
||||
private String path;
|
||||
private String url;
|
||||
private String repositoryRoot;
|
||||
private String repositoryUuid;
|
||||
private String revision;
|
||||
private String nodeKind;
|
||||
private String schedule;
|
||||
private String lastChangedAuthor;
|
||||
private String lastChangedRev;
|
||||
private String lastChangedDate;
|
||||
|
||||
public SvnInfo() {
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getRepositoryRoot() {
|
||||
return repositoryRoot;
|
||||
}
|
||||
|
||||
public void setRepositoryRoot(String repositoryRoot) {
|
||||
this.repositoryRoot = repositoryRoot;
|
||||
}
|
||||
|
||||
public String getRepositoryUuid() {
|
||||
return repositoryUuid;
|
||||
}
|
||||
|
||||
public void setRepositoryUuid(String repositoryUuid) {
|
||||
this.repositoryUuid = repositoryUuid;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public String getNodeKind() {
|
||||
return nodeKind;
|
||||
}
|
||||
|
||||
public void setNodeKind(String nodeKind) {
|
||||
this.nodeKind = nodeKind;
|
||||
}
|
||||
|
||||
public String getSchedule() {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public void setSchedule(String schedule) {
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
public String getLastChangedAuthor() {
|
||||
return lastChangedAuthor;
|
||||
}
|
||||
|
||||
public void setLastChangedAuthor(String lastChangedAuthor) {
|
||||
this.lastChangedAuthor = lastChangedAuthor;
|
||||
}
|
||||
|
||||
public String getLastChangedRev() {
|
||||
return lastChangedRev;
|
||||
}
|
||||
|
||||
public void setLastChangedRev(String lastChangedRev) {
|
||||
this.lastChangedRev = lastChangedRev;
|
||||
}
|
||||
|
||||
public String getLastChangedDate() {
|
||||
return lastChangedDate;
|
||||
}
|
||||
|
||||
public void setLastChangedDate(String lastChangedDate) {
|
||||
this.lastChangedDate = lastChangedDate;
|
||||
}
|
||||
}
|
||||
72
src/main/java/com/svnmanager/model/SvnLog.java
Normal file
72
src/main/java/com/svnmanager/model/SvnLog.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package com.svnmanager.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SVN日志模型
|
||||
*/
|
||||
public class SvnLog {
|
||||
private String revision;
|
||||
private String author;
|
||||
private LocalDateTime date;
|
||||
private String message;
|
||||
private List<String> changedPaths;
|
||||
|
||||
public SvnLog() {
|
||||
this.changedPaths = new ArrayList<>();
|
||||
}
|
||||
|
||||
public SvnLog(String revision, String author, LocalDateTime date, String message) {
|
||||
this.revision = revision;
|
||||
this.author = author;
|
||||
this.date = date;
|
||||
this.message = message;
|
||||
this.changedPaths = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public LocalDateTime getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDateTime date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public List<String> getChangedPaths() {
|
||||
return changedPaths;
|
||||
}
|
||||
|
||||
public void setChangedPaths(List<String> changedPaths) {
|
||||
this.changedPaths = changedPaths;
|
||||
}
|
||||
|
||||
public void addChangedPath(String path) {
|
||||
this.changedPaths.add(path);
|
||||
}
|
||||
}
|
||||
95
src/main/java/com/svnmanager/model/SvnStatus.java
Normal file
95
src/main/java/com/svnmanager/model/SvnStatus.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package com.svnmanager.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SVN状态模型
|
||||
*/
|
||||
public class SvnStatus {
|
||||
private String workingCopyPath;
|
||||
private String revision;
|
||||
private List<SvnFileStatus> files;
|
||||
private int modifiedCount;
|
||||
private int addedCount;
|
||||
private int deletedCount;
|
||||
private int conflictedCount;
|
||||
|
||||
public SvnStatus() {
|
||||
this.files = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getWorkingCopyPath() {
|
||||
return workingCopyPath;
|
||||
}
|
||||
|
||||
public void setWorkingCopyPath(String workingCopyPath) {
|
||||
this.workingCopyPath = workingCopyPath;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public List<SvnFileStatus> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFiles(List<SvnFileStatus> files) {
|
||||
this.files = files;
|
||||
updateCounts();
|
||||
}
|
||||
|
||||
public void addFile(SvnFileStatus file) {
|
||||
this.files.add(file);
|
||||
updateCounts();
|
||||
}
|
||||
|
||||
public int getModifiedCount() {
|
||||
return modifiedCount;
|
||||
}
|
||||
|
||||
public int getAddedCount() {
|
||||
return addedCount;
|
||||
}
|
||||
|
||||
public int getDeletedCount() {
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
public int getConflictedCount() {
|
||||
return conflictedCount;
|
||||
}
|
||||
|
||||
public int getTotalChangedFiles() {
|
||||
return modifiedCount + addedCount + deletedCount + conflictedCount;
|
||||
}
|
||||
|
||||
private void updateCounts() {
|
||||
modifiedCount = 0;
|
||||
addedCount = 0;
|
||||
deletedCount = 0;
|
||||
conflictedCount = 0;
|
||||
|
||||
for (SvnFileStatus file : files) {
|
||||
switch (file.getStatus()) {
|
||||
case MODIFIED:
|
||||
modifiedCount++;
|
||||
break;
|
||||
case ADDED:
|
||||
addedCount++;
|
||||
break;
|
||||
case DELETED:
|
||||
deletedCount++;
|
||||
break;
|
||||
case CONFLICTED:
|
||||
conflictedCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/main/java/com/svnmanager/service/CheckoutService.java
Normal file
67
src/main/java/com/svnmanager/service/CheckoutService.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Checkout服务
|
||||
*/
|
||||
public class CheckoutService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CheckoutService.class);
|
||||
|
||||
/**
|
||||
* 检出SVN仓库
|
||||
*
|
||||
* @param svnUrl SVN仓库URL
|
||||
* @param targetPath 目标路径
|
||||
* @param revision 版本号(可选,null表示最新版本)
|
||||
* @return 是否成功
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public boolean checkout(String svnUrl, String targetPath, String revision)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.info("检出仓库: {} 到 {}", svnUrl, targetPath);
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(svnUrl);
|
||||
args.add(targetPath);
|
||||
|
||||
if (revision != null && !revision.isEmpty()) {
|
||||
args.add("-r");
|
||||
args.add(revision);
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("checkout", args, null);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
logger.info("检出成功");
|
||||
return true;
|
||||
} else {
|
||||
logger.error("检出失败: {}", result.getErrorAsString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检出SVN仓库(最新版本)
|
||||
*
|
||||
* @param svnUrl SVN仓库URL
|
||||
* @param targetPath 目标路径
|
||||
* @return 是否成功
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public boolean checkout(String svnUrl, String targetPath)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return checkout(svnUrl, targetPath, null);
|
||||
}
|
||||
}
|
||||
134
src/main/java/com/svnmanager/service/CommitService.java
Normal file
134
src/main/java/com/svnmanager/service/CommitService.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Commit服务
|
||||
*/
|
||||
public class CommitService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommitService.class);
|
||||
|
||||
/**
|
||||
* 提交修改
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @param message 提交消息
|
||||
* @param files 要提交的文件列表(null表示提交所有修改)
|
||||
* @return 提交结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public CommitResult commit(String workingDirectory, String message, List<String> files)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.info("提交修改: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
if (message == null || message.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("提交消息不能为空");
|
||||
}
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-m");
|
||||
args.add(message);
|
||||
|
||||
if (files != null && !files.isEmpty()) {
|
||||
args.addAll(files);
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("commit", args, workingDirectory);
|
||||
|
||||
CommitResult commitResult = new CommitResult();
|
||||
commitResult.setSuccess(result.isSuccess());
|
||||
commitResult.setOutput(result.getOutputAsString());
|
||||
commitResult.setError(result.getErrorAsString());
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// 解析提交后的版本号
|
||||
String output = result.getOutputAsString();
|
||||
String revisionLine = output.lines()
|
||||
.filter(line -> line.contains("Committed revision"))
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
if (!revisionLine.isEmpty()) {
|
||||
String[] parts = revisionLine.split(" ");
|
||||
if (parts.length > 0) {
|
||||
String rev = parts[parts.length - 1].replace(".", "");
|
||||
commitResult.setRevision(rev);
|
||||
}
|
||||
}
|
||||
logger.info("提交成功,版本: {}", commitResult.getRevision());
|
||||
} else {
|
||||
logger.error("提交失败: {}", result.getErrorAsString());
|
||||
}
|
||||
|
||||
return commitResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交所有修改
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @param message 提交消息
|
||||
* @return 提交结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public CommitResult commit(String workingDirectory, String message)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return commit(workingDirectory, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交结果
|
||||
*/
|
||||
public static class CommitResult {
|
||||
private boolean success;
|
||||
private String revision;
|
||||
private String output;
|
||||
private String error;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public String getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void setOutput(String output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/svnmanager/service/DiffService.java
Normal file
64
src/main/java/com/svnmanager/service/DiffService.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Diff服务
|
||||
*/
|
||||
public class DiffService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DiffService.class);
|
||||
|
||||
/**
|
||||
* 获取差异
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @param filePath 文件路径(null表示所有文件)
|
||||
* @return 差异内容
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public String getDiff(String workingDirectory, String filePath)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.debug("获取差异: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
if (filePath != null && !filePath.isEmpty()) {
|
||||
args.add(filePath);
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("diff", args, workingDirectory);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return result.getOutputAsString();
|
||||
} else {
|
||||
logger.warn("获取差异失败: {}", result.getErrorAsString());
|
||||
return result.getErrorAsString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文件的差异
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 差异内容
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public String getDiff(String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return getDiff(workingDirectory, null);
|
||||
}
|
||||
}
|
||||
84
src/main/java/com/svnmanager/service/InfoService.java
Normal file
84
src/main/java/com/svnmanager/service/InfoService.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.model.SvnInfo;
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Info服务
|
||||
*/
|
||||
public class InfoService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InfoService.class);
|
||||
|
||||
/**
|
||||
* 获取SVN信息
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return SVN信息
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public SvnInfo getInfo(String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.debug("获取信息: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("info", workingDirectory);
|
||||
|
||||
SvnInfo info = new SvnInfo();
|
||||
if (result.isSuccess()) {
|
||||
// 将输出行合并为一个字符串,便于解析
|
||||
String output = String.join("\n", result.getOutput());
|
||||
parseInfoOutput(output, info);
|
||||
} else {
|
||||
logger.warn("获取信息失败: {}", result.getErrorAsString());
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析svn info输出
|
||||
*
|
||||
* @param output 命令输出
|
||||
* @param info 信息对象
|
||||
*/
|
||||
private void parseInfoOutput(String output, SvnInfo info) {
|
||||
for (String line : output.split("\n")) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("Path: ")) {
|
||||
info.setPath(line.substring(6).trim());
|
||||
} else if (line.startsWith("URL: ")) {
|
||||
info.setUrl(line.substring(5).trim());
|
||||
} else if (line.startsWith("Repository Root: ")) {
|
||||
info.setRepositoryRoot(line.substring(17).trim());
|
||||
} else if (line.startsWith("Repository UUID: ")) {
|
||||
info.setRepositoryUuid(line.substring(18).trim());
|
||||
} else if (line.startsWith("Revision: ")) {
|
||||
info.setRevision(line.substring(11).trim());
|
||||
} else if (line.startsWith("Node Kind: ")) {
|
||||
info.setNodeKind(line.substring(11).trim());
|
||||
} else if (line.startsWith("Schedule: ")) {
|
||||
info.setSchedule(line.substring(11).trim());
|
||||
} else if (line.startsWith("Last Changed Author: ")) {
|
||||
info.setLastChangedAuthor(line.substring(22).trim());
|
||||
} else if (line.startsWith("Last Changed Rev: ")) {
|
||||
info.setLastChangedRev(line.substring(19).trim());
|
||||
} else if (line.startsWith("Last Changed Date: ")) {
|
||||
info.setLastChangedDate(line.substring(20).trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/main/java/com/svnmanager/service/LogService.java
Normal file
139
src/main/java/com/svnmanager/service/LogService.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.model.SvnLog;
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Log服务
|
||||
*/
|
||||
public class LogService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogService.class);
|
||||
private static final DateTimeFormatter SVN_DATE_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 获取SVN日志
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @param limit 限制条数(null表示不限制)
|
||||
* @return 日志列表
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public List<SvnLog> getLog(String workingDirectory, Integer limit)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.debug("获取日志: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-v"); // 详细输出
|
||||
if (limit != null && limit > 0) {
|
||||
args.add("-l");
|
||||
args.add(String.valueOf(limit));
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("log", args, workingDirectory);
|
||||
|
||||
List<SvnLog> logs = new ArrayList<>();
|
||||
if (result.isSuccess()) {
|
||||
// 将输出行合并为一个字符串,便于解析
|
||||
String output = String.join("\n", result.getOutput());
|
||||
parseLogOutput(output, logs);
|
||||
} else {
|
||||
logger.warn("获取日志失败: {}", result.getErrorAsString());
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SVN日志(默认限制50条)
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 日志列表
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public List<SvnLog> getLog(String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return getLog(workingDirectory, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析svn log输出
|
||||
*
|
||||
* @param output 命令输出
|
||||
* @param logs 日志列表
|
||||
*/
|
||||
private void parseLogOutput(String output, List<SvnLog> logs) {
|
||||
String[] lines = output.split("\n");
|
||||
SvnLog currentLog = null;
|
||||
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
|
||||
if (line.startsWith("------------------------------------------------------------------------")) {
|
||||
if (currentLog != null) {
|
||||
logs.add(currentLog);
|
||||
}
|
||||
currentLog = new SvnLog();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentLog == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("r")) {
|
||||
// 解析版本号、作者、日期
|
||||
// 格式: "r1234 | author | 2024-01-01 12:00:00 +0800 (Mon, 01 Jan 2024) | 1 line"
|
||||
String[] parts = line.split("\\|");
|
||||
if (parts.length >= 3) {
|
||||
currentLog.setRevision(parts[0].trim());
|
||||
currentLog.setAuthor(parts[1].trim());
|
||||
|
||||
// 解析日期
|
||||
String dateStr = parts[2].trim();
|
||||
String datePart = dateStr.split("\\(")[0].trim();
|
||||
try {
|
||||
LocalDateTime date = LocalDateTime.parse(datePart, SVN_DATE_FORMATTER);
|
||||
currentLog.setDate(date);
|
||||
} catch (Exception e) {
|
||||
logger.debug("解析日期失败: {}", datePart);
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("Changed paths:")) {
|
||||
// 跳过Changed paths标题
|
||||
continue;
|
||||
} else if (line.startsWith(" ")) {
|
||||
// 变更路径
|
||||
String path = line.trim();
|
||||
currentLog.addChangedPath(path);
|
||||
} else if (!line.isEmpty() && currentLog.getMessage() == null) {
|
||||
// 提交消息
|
||||
currentLog.setMessage(line);
|
||||
} else if (!line.isEmpty() && currentLog.getMessage() != null) {
|
||||
// 追加提交消息(多行)
|
||||
currentLog.setMessage(currentLog.getMessage() + "\n" + line);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLog != null) {
|
||||
logs.add(currentLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/main/java/com/svnmanager/service/StatusService.java
Normal file
102
src/main/java/com/svnmanager/service/StatusService.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.model.SvnFileStatus;
|
||||
import com.svnmanager.model.SvnStatus;
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Status服务
|
||||
*/
|
||||
public class StatusService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(StatusService.class);
|
||||
|
||||
/**
|
||||
* 获取工作副本状态
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return SVN状态
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public SvnStatus getStatus(String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.debug("获取状态: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-v"); // 详细输出
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("status", args, workingDirectory);
|
||||
|
||||
SvnStatus status = new SvnStatus();
|
||||
status.setWorkingCopyPath(workingDirectory);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// 将输出行合并为一个字符串,便于解析
|
||||
String output = String.join("\n", result.getOutput());
|
||||
parseStatusOutput(output, status);
|
||||
} else {
|
||||
logger.warn("获取状态失败: {}", result.getErrorAsString());
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析svn status输出
|
||||
*
|
||||
* @param output 命令输出
|
||||
* @param status 状态对象
|
||||
*/
|
||||
private void parseStatusOutput(String output, SvnStatus status) {
|
||||
List<SvnFileStatus> files = new ArrayList<>();
|
||||
|
||||
for (String line : output.split("\n")) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// SVN status格式: "状态码 工作副本状态 版本号 文件路径"
|
||||
// 例如: "M 1234 src/Main.java"
|
||||
if (line.length() < 8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char workingCopyStatus = line.charAt(0);
|
||||
char repositoryStatus = line.length() > 1 ? line.charAt(1) : ' ';
|
||||
|
||||
// 跳过标题行
|
||||
if (workingCopyStatus == 'S' && line.contains("Status")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SvnFileStatus.FileStatus fileStatus = SvnFileStatus.FileStatus.fromCode(workingCopyStatus);
|
||||
|
||||
// 提取文件路径(跳过状态码和版本号)
|
||||
String filePath = line.substring(8).trim();
|
||||
if (filePath.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SvnFileStatus fileStatusObj = new SvnFileStatus(filePath, fileStatus);
|
||||
fileStatusObj.setWorkingCopyStatus(String.valueOf(workingCopyStatus));
|
||||
fileStatusObj.setRepositoryStatus(String.valueOf(repositoryStatus));
|
||||
|
||||
files.add(fileStatusObj);
|
||||
}
|
||||
|
||||
status.setFiles(files);
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/svnmanager/service/SvnService.java
Normal file
64
src/main/java/com/svnmanager/service/SvnService.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* SVN服务基类
|
||||
*/
|
||||
public abstract class SvnService {
|
||||
protected static final Logger logger = LoggerFactory.getLogger(SvnService.class);
|
||||
|
||||
/**
|
||||
* 执行SVN命令
|
||||
*
|
||||
* @param svnCommand SVN子命令
|
||||
* @param args 命令参数
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 命令执行结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
protected ProcessUtil.ProcessResult executeSvnCommand(String svnCommand, List<String> args, String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return ProcessUtil.executeSvnCommand(svnCommand, args, workingDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SVN命令(无参数)
|
||||
*
|
||||
* @param svnCommand SVN子命令
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 命令执行结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
protected ProcessUtil.ProcessResult executeSvnCommand(String svnCommand, String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return executeSvnCommand(svnCommand, new ArrayList<>(), workingDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证工作目录是否为有效的SVN工作副本
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 是否为有效的SVN工作副本
|
||||
*/
|
||||
protected boolean isValidWorkingCopy(String workingDirectory) {
|
||||
try {
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("info", workingDirectory);
|
||||
return result.isSuccess();
|
||||
} catch (Exception e) {
|
||||
logger.debug("验证工作副本失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/main/java/com/svnmanager/service/UpdateService.java
Normal file
126
src/main/java/com/svnmanager/service/UpdateService.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package com.svnmanager.service;
|
||||
|
||||
import com.svnmanager.util.ProcessUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Update服务
|
||||
*/
|
||||
public class UpdateService extends SvnService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(UpdateService.class);
|
||||
|
||||
/**
|
||||
* 更新工作副本
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @param revision 版本号(可选,null表示最新版本)
|
||||
* @return 更新结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public UpdateResult update(String workingDirectory, String revision)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.info("更新工作副本: {}", workingDirectory);
|
||||
|
||||
if (!isValidWorkingCopy(workingDirectory)) {
|
||||
throw new IllegalArgumentException("无效的SVN工作副本: " + workingDirectory);
|
||||
}
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
if (revision != null && !revision.isEmpty()) {
|
||||
args.add("-r");
|
||||
args.add(revision);
|
||||
}
|
||||
|
||||
ProcessUtil.ProcessResult result = executeSvnCommand("update", args, workingDirectory);
|
||||
|
||||
UpdateResult updateResult = new UpdateResult();
|
||||
updateResult.setSuccess(result.isSuccess());
|
||||
updateResult.setOutput(result.getOutputAsString());
|
||||
updateResult.setError(result.getErrorAsString());
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// 解析更新后的版本号
|
||||
String output = result.getOutputAsString();
|
||||
String revisionLine = output.lines()
|
||||
.filter(line -> line.contains("Updated to revision"))
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
if (!revisionLine.isEmpty()) {
|
||||
String[] parts = revisionLine.split(" ");
|
||||
if (parts.length > 0) {
|
||||
String rev = parts[parts.length - 1].replace(".", "");
|
||||
updateResult.setRevision(rev);
|
||||
}
|
||||
}
|
||||
logger.info("更新成功,版本: {}", updateResult.getRevision());
|
||||
} else {
|
||||
logger.error("更新失败: {}", result.getErrorAsString());
|
||||
}
|
||||
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新工作副本到最新版本
|
||||
*
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 更新结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public UpdateResult update(String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return update(workingDirectory, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新结果
|
||||
*/
|
||||
public static class UpdateResult {
|
||||
private boolean success;
|
||||
private String revision;
|
||||
private String output;
|
||||
private String error;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public String getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void setOutput(String output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/main/java/com/svnmanager/util/ConfigUtil.java
Normal file
157
src/main/java/com/svnmanager/util/ConfigUtil.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package com.svnmanager.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
import com.svnmanager.model.Project;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 配置管理工具类
|
||||
*/
|
||||
public class ConfigUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigUtil.class);
|
||||
private static final String CONFIG_DIR = System.getProperty("user.home") + File.separator + ".svn-manager";
|
||||
private static final String PROJECTS_FILE = CONFIG_DIR + File.separator + "projects.json";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
// 确保配置目录存在
|
||||
try {
|
||||
Path configPath = Paths.get(CONFIG_DIR);
|
||||
if (!Files.exists(configPath)) {
|
||||
Files.createDirectories(configPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("创建配置目录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载项目列表
|
||||
*
|
||||
* @return 项目列表
|
||||
*/
|
||||
public static List<Project> loadProjects() {
|
||||
File file = new File(PROJECTS_FILE);
|
||||
if (!file.exists()) {
|
||||
logger.info("项目配置文件不存在,返回空列表");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
try {
|
||||
CollectionType listType = objectMapper.getTypeFactory()
|
||||
.constructCollectionType(List.class, Project.class);
|
||||
List<Project> projects = objectMapper.readValue(file, listType);
|
||||
logger.info("成功加载 {} 个项目", projects.size());
|
||||
return projects;
|
||||
} catch (IOException e) {
|
||||
logger.error("加载项目配置失败", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存项目列表
|
||||
*
|
||||
* @param projects 项目列表
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
public static boolean saveProjects(List<Project> projects) {
|
||||
File file = new File(PROJECTS_FILE);
|
||||
try {
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValue(file, projects);
|
||||
logger.info("成功保存 {} 个项目", projects.size());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("保存项目配置失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加项目
|
||||
*
|
||||
* @param project 项目
|
||||
* @return 是否添加成功
|
||||
*/
|
||||
public static boolean addProject(Project project) {
|
||||
if (project.getId() == null || project.getId().isEmpty()) {
|
||||
project.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
List<Project> projects = loadProjects();
|
||||
projects.add(project);
|
||||
return saveProjects(projects);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*
|
||||
* @param project 项目
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
public static boolean updateProject(Project project) {
|
||||
List<Project> projects = loadProjects();
|
||||
for (int i = 0; i < projects.size(); i++) {
|
||||
if (projects.get(i).getId().equals(project.getId())) {
|
||||
projects.set(i, project);
|
||||
return saveProjects(projects);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
public static boolean deleteProject(String projectId) {
|
||||
List<Project> projects = loadProjects();
|
||||
projects.removeIf(p -> p.getId().equals(projectId));
|
||||
return saveProjects(projects);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取项目
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 项目,如果不存在返回null
|
||||
*/
|
||||
public static Project getProjectById(String projectId) {
|
||||
List<Project> projects = loadProjects();
|
||||
return projects.stream()
|
||||
.filter(p -> p.getId().equals(projectId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置目录路径
|
||||
*
|
||||
* @return 配置目录路径
|
||||
*/
|
||||
public static String getConfigDir() {
|
||||
return CONFIG_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目配置文件路径
|
||||
*
|
||||
* @return 项目配置文件路径
|
||||
*/
|
||||
public static String getProjectsFilePath() {
|
||||
return PROJECTS_FILE;
|
||||
}
|
||||
}
|
||||
51
src/main/java/com/svnmanager/util/LogUtil.java
Normal file
51
src/main/java/com/svnmanager/util/LogUtil.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.svnmanager.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 日志工具类
|
||||
*/
|
||||
public class LogUtil {
|
||||
|
||||
/**
|
||||
* 获取Logger实例
|
||||
*
|
||||
* @param clazz 类
|
||||
* @return Logger实例
|
||||
*/
|
||||
public static Logger getLogger(Class<?> clazz) {
|
||||
return LoggerFactory.getLogger(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误日志
|
||||
*
|
||||
* @param logger Logger实例
|
||||
* @param message 消息
|
||||
* @param throwable 异常
|
||||
*/
|
||||
public static void logError(Logger logger, String message, Throwable throwable) {
|
||||
logger.error(message, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录信息日志
|
||||
*
|
||||
* @param logger Logger实例
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void logInfo(Logger logger, String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录调试日志
|
||||
*
|
||||
* @param logger Logger实例
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void logDebug(Logger logger, String message) {
|
||||
logger.debug(message);
|
||||
}
|
||||
}
|
||||
159
src/main/java/com/svnmanager/util/ProcessUtil.java
Normal file
159
src/main/java/com/svnmanager/util/ProcessUtil.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package com.svnmanager.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 进程执行工具类
|
||||
*/
|
||||
public class ProcessUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessUtil.class);
|
||||
private static final int DEFAULT_TIMEOUT_SECONDS = 300;
|
||||
|
||||
/**
|
||||
* 执行命令并返回输出
|
||||
*
|
||||
* @param command 命令数组
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 命令输出行列表
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public static ProcessResult executeCommand(String[] command, String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
return executeCommand(command, workingDirectory, DEFAULT_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令并返回输出
|
||||
*
|
||||
* @param command 命令数组
|
||||
* @param workingDirectory 工作目录
|
||||
* @param timeoutSeconds 超时时间(秒)
|
||||
* @return 命令输出结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public static ProcessResult executeCommand(String[] command, String workingDirectory, int timeoutSeconds)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
logger.debug("执行命令: {}", String.join(" ", command));
|
||||
logger.debug("工作目录: {}", workingDirectory);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
if (workingDirectory != null && !workingDirectory.isEmpty()) {
|
||||
processBuilder.directory(new java.io.File(workingDirectory));
|
||||
}
|
||||
|
||||
processBuilder.redirectErrorStream(true);
|
||||
Process process = processBuilder.start();
|
||||
|
||||
List<String> outputLines = new ArrayList<>();
|
||||
List<String> errorLines = new ArrayList<>();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream(), Charset.defaultCharset()));
|
||||
BufferedReader errorReader = new BufferedReader(
|
||||
new InputStreamReader(process.getErrorStream(), Charset.defaultCharset()))) {
|
||||
|
||||
// 读取标准输出
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
outputLines.add(line);
|
||||
}
|
||||
|
||||
// 读取错误输出
|
||||
while ((line = errorReader.readLine()) != null) {
|
||||
errorLines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (!finished) {
|
||||
process.destroyForcibly();
|
||||
throw new TimeoutException("命令执行超时: " + String.join(" ", command));
|
||||
}
|
||||
|
||||
int exitCode = process.exitValue();
|
||||
ProcessResult result = new ProcessResult(exitCode, outputLines, errorLines);
|
||||
|
||||
logger.debug("命令执行完成,退出码: {}", exitCode);
|
||||
if (exitCode != 0) {
|
||||
logger.warn("命令执行失败: {}", String.join("\n", errorLines));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SVN命令
|
||||
*
|
||||
* @param svnCommand SVN子命令(如 "status", "update")
|
||||
* @param args 命令参数
|
||||
* @param workingDirectory 工作目录
|
||||
* @return 命令输出结果
|
||||
* @throws IOException IO异常
|
||||
* @throws InterruptedException 中断异常
|
||||
* @throws TimeoutException 超时异常
|
||||
*/
|
||||
public static ProcessResult executeSvnCommand(String svnCommand, List<String> args, String workingDirectory)
|
||||
throws IOException, InterruptedException, TimeoutException {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("svn");
|
||||
command.add(svnCommand);
|
||||
if (args != null) {
|
||||
command.addAll(args);
|
||||
}
|
||||
|
||||
return executeCommand(command.toArray(new String[0]), workingDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进程执行结果
|
||||
*/
|
||||
public static class ProcessResult {
|
||||
private final int exitCode;
|
||||
private final List<String> output;
|
||||
private final List<String> error;
|
||||
|
||||
public ProcessResult(int exitCode, List<String> output, List<String> error) {
|
||||
this.exitCode = exitCode;
|
||||
this.output = output;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public List<String> getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public List<String> getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public String getOutputAsString() {
|
||||
return String.join("\n", output);
|
||||
}
|
||||
|
||||
public String getErrorAsString() {
|
||||
return String.join("\n", error);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return exitCode == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user